1.继承Thread类
在继承Thread类后重写run()方法,然后直接实例化子类对象即可创建线程,线程的启动通过调用start()方法,这里有一点需要说明一下,启动线程后线程并不是直接运行,而是进入就绪状态,在抢占到CPU之后才会真正开始运行代码。
public class JavaTest extends Thread{
@Override
public void run() {
//重写run()方法,告诉这个线程应该做什么事。
}
}
2.实现Runnable接口
实现Runnable接口后重写run()方法,然后,将继承接口的类实例化,再实例化一个Thread线程,将实现接口类的实例化对象通过构造方法,放入线程中,再调用线程的start()方法即可。
public class JavaTest implements Runnable{
@Override
public void run() {
//重写run()方法,告诉这个线程应该做什么事。
}
public static void main(String[] args) {
Runnable runnable = new JavaTest();
Thread thread = new Thread();
thread.start();
}
}
3.实现Callable接口
实现Callable接口后需要重写Callable接口的call()方法,然后还要将其通过构造器传给FutureTask()对象,FutureTask类是一个是实现了Runable接口和Futere接口的类,所以FutureTask对象是可以被Thread对象接受应且启动的,Future接口中定义了get() isCancel() isDone()等方法所以可以通过FutureTsak对象来接收返回值,从而判断线程的状态。
不过需要注意的是实现Callable接口的时候因为有返回值,所以这个接口是带有泛型参数的。
public class JavaTest implements Callable {
@Override
public V call() throws Exception {
return null;
}
}
class a {
public static void main(String[] args) {
Callable callable = new JavaTest();
FutureTask futureTask = new FutureTask(callable);
Thread thread = new Thread(futureTask);
thread.start();
}
}
这就是创建线程的三种方法。
使用多线程引起的线程安全问题1.脏读
什么是脏读呢?
就是当某个线程去读取数据的时候,数据已经被另一个线程修改了,这时候,读取到了数据是之前没有被修改的数据,而不是最新的已将被修改过后的数据,这就是脏读。
public class JavaTest {
private String name = "nihao";
private volatile int money = 88;
public void getNameAndMoney() {
int a = money;
System.out.println(name+""+a);;
}
public void setNameAndMoney(String name,int money) {
this.name = name;
//因为thread 线程调用发发的时候CPU可能会非
// 常快的将两条指令处理完,所以在这里让线程
//停止两秒放大脏读现象产生的机率。
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.money = money;
}
public static void main(String[] args) {
JavaTest j = new JavaTest();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
j.setNameAndMoney("nibuhao",99);
}
});
thread.start();
//main 线程sleep 一秒给thread 线程启动的时间
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
j.getNameAndMoney();
}
}
//打印结果
//nibuhao88
可以看出,在读取到的数据中,money值并不是99;这就引申出了多线程情况下,数据的线程安全问题,而要解决线程安全问题,就要满足三个特性。
1、满足操作的原子性,原子性操作简单来说就是,不可打断的操作,他不会被线程调度器,也就是说CPU打断,也就是说这个操作要么成功要么失败。
比如说 int i= 1;这个操作就是原子性的,而 i++;这个操作,会被分解成读取i的值 给i+1, 将修改后的值重新赋值给 i,这个操作可以看作是先读 后写,在着之间的时间是有可能被打断的。
2、满足数据的可见性,可见性是指在线程1堆数据进行修改后,还没有来得及将修改后的数据写入,这时候线程2去读取了数据,线程2并不知道数据已经被修改了,这样就会造成错误,线程2如果和线程一进行同养的修改操作,那么本来数据是加了两次,但是实际上数据的值只增加了1。
看起来和原子性有些相似,但是是不一样的,原子性是保证操作要么进行,要么不进行,如果发生了被打断的问题,就会认为这次操作没有成功,再次调度的时候是不会接着被打断的操作去执行的,而是会重新进行一次操作,类似于数据库中事务的回滚。
3、指令有序性
JVM在底层运行代码的时候,会对指令进行优化,而这时候就会产生问题,比如单例模式 中饿汉模式双重优化锁机制,中会在实例化对象的时候进行指令的重排序,这样很有可能会导致问题的出现,有兴趣的小伙伴可以去看看我的另一篇博客https://blog.csdn.net/ETpgmer/article/details/104458470
后面我会将如何解决这些因为多线程产生的问题的方法放在我的博客中。希望有兴趣的小伙伴可以一起学习,当然如果发现我的文章中有错误的地方也可以在评论区指出,我们共同探讨。