浅谈java中的多线程

什么是多线程?

多线程通俗的来说就是同时有多个线程在运行,但是运行是在并发执行,即并行,而单线程就是事情必须按照顺序来执行,只能在执行完一件事情之后才能执行下一件事情。
每当建立一个线程就会有一个线程栈,在线程中运行哪个方法,哪个方法就会入栈,方法执行完之后就会出栈,当线程栈中没有任何要执行的方法的时候就是一个线程执行完毕了。
我们所说的main方法就是一个主线程。

多线程举例

我们先来建一个线程类,这个类继承了Java提供的Thread类

public class ThreadTest extends Thread{
    private static int a=0;
    private String name;
    public ThreadTest(String name){
        this.name=name;
    }
    public void run(){
        for (int i=0;i<20;i++){
            a++;
            System.out.println(this.name+"-----"+a);
        }
    }
}

然后在主函数中执行

public class Main {
    public static void main(String[] args){
        ThreadTest t1=new ThreadTest("线程1");
        t1.start();
    }
}

主函数中只有一个线程在执行,我们来看一下结果:
在这里插入图片描述
这个显然是我们想要的结果,接下来我们为main函数加上一句输出

public class Main {
    public static void main(String[] args){
        ThreadTest t1=new ThreadTest("线程1");
        t1.start();
        System.out.println("hello world");
    }
}

我们的本意是想让线程t1执行完毕之后才输出hello world,但是我们来看一下结果:
在这里插入图片描述
可以看到主线程并不会等待t1执行完之后才输出,怎么让他输出我们想要的结果呢?
我们给主函数再加点东西

public class Main {
    public static void main(String[] args){
        ThreadTest t1=new ThreadTest("线程1");
        t1.start();
        try {
            t1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("hello world");
    }
}

再看执行结果:
在这里插入图片描述
那么这个join()方法的功能就出来啦!join()方法可以让之后的代码等待t1执行完之后才执行
我们再对main方法进行修改

public class Main {
    public static void main(String[] args){
        ThreadTest t1=new ThreadTest("线程1");
        t1.start();
        try {
            t1.sleep(6000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("hello world");
    }
}

执行之后就会发现,hello world这句话在t1执行完之后延迟6000ms出现,那么sleep()方法的作用也就有了,sleep()方法可以让线程“沉睡”一段时间之后接着执行
接下来我们再增加一个线程

public class Main {
    public static void main(String[] args){
        ThreadTest t1=new ThreadTest("线程1");
        ThreadTest t2=new ThreadTest("线程2");
        t1.start();
        t2.start();
    }
}

为了方便,我们把ThreadTest类中run()方法里的循环次数改为10
我们来看一下运行结果:
在这里插入图片描述
乍一看没有什么问题,就是顺序可能和我们想的不一样,但这是多线程的正常现象。
那如果我们把ThreadTest类改成下面的:

public class ThreadTest extends Thread{
    private static int a=0;
    private String name;
    public ThreadTest(String name){
        this.name=name;
    }
    public void run(){
        for (int i=0;i<10;i++){
            a++;
            System.out.println(this.name+"-----"+a);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

我们再来看一下执行结果:
在这里插入图片描述
我们就会看到重复的输出,这就是我们所说的线程不安全,我们不能让多线程在“沉睡”几秒之后执行结果就不对了,我们要保证在任何情况下,多线程执行的结果都是我们想要的结果,这样才是线程安全的。
那么怎样保证线程安全呢?

实现线程安全的方法—锁

为了不让结果出现上面的情况,我们引入了锁的概念。在上面的例子中,a是一个共享变量,有可能在一个线程访问它的时候,另一个线程也在访问,若两个线程访问的时候读到的数据都是1,那么写回的时候就会都是2,这就出现了上面输出重复数据的情况,也就是说有线程读取的是“脏数据”。
为了避免上述情况发生,我们写一个类,采用公共对象的方式来实现加锁。

public class DataTest {
    private static int a=0;
    //对直接修改共享变量的方法进行加锁
    public synchronized void add(String name){
        if(a==20){
            return;
        }
        if(a<20){
            a++;
            //name是线程的名字
            System.out.println(name+"----"+a);
        }
    }
}

线程类做一些改变

public class ThreadTest extends Thread{
    private String name;
    //添加一个公共对象
    private DataTest d;
    public ThreadTest(String name,DataTest d){
        this.name=name;
        this.d=d;
    }
    public void run(){
        for(int i=0;i<10;i++){
       		//调用公共对象d的add方法
            d.add(name);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

main方法如下:

public class Main {
    public static void main(String[] args){
    	//创建一个公共对象
        DataTest d=new DataTest();
        ThreadTest t1=new ThreadTest("线程1",d);
        ThreadTest t2=new ThreadTest("线程2",d);
        t1.start();
        t2.start();
    }
}

我们来看一下执行结果:
在这里插入图片描述
可以看到执行结果是正确的,证明我们加的锁起作用了。
这里注意一下:只能对方法加锁,不能对公共变量进行加锁,但是对方法加锁,锁的不是方法,而是方法里面的共享变量。
加锁要对直接操作共享变量的方法进行加锁,特别是共享资源是引用类型里的一部分时

类锁、对象锁

对类中操作同一共享资源的所有非静态方法进行加锁,所有操作这一共享资源的非静态方法会按顺序执行,即互相锁定(注意非静态方法操作的是同一个对象,否则会锁不住),静态方法加锁也是可以互相锁定的。这两个相互锁定都有一个前提条件:必须操作的同一共享资源
对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。
静态方法的锁属于类锁,非静态方法的锁属于对象锁,所以静态锁和非静态锁是互相锁不住的。
类中定义了两个非静态属性,两个非静态方法加锁之后,当其中一个加锁方法访问其中一个属性时,另一个加锁方法就不能再访问另一个属性,因为一个非静态方法加锁之后会锁住整个对象,这就是对象锁的作用。
最后我们要知道:线程加锁会使程序变慢哦!

发布了8 篇原创文章 · 获赞 0 · 访问量 79

猜你喜欢

转载自blog.csdn.net/qq_43677686/article/details/104072502