java基础(5.8)java高级基础之线程——synchronized关键字
本章主要讲线程,至于带上进程的原因是为了方便大家对进程和线程有个概念(咳,主要原因是鄙人也不太会进程~)。
首先简单说下我个人对进程和线程的理解。我们的java是运行在jvm虚拟机下的程序,我们每多启动一个jvm就是多了启动了一个进程。至于线程,就是每个进程下多开一个和现有的的任务同时执行的任务。
- 我们一个程序至少包含一个进程,一个进程至少包含一个线程
- 线程是从系统获取资源,有自己独立的空间,线程从进程中获取资源,没有自己独立的空间,因此进程之间不会相互影响而线程之间会相互影响,比如资源,数据等。
下面主要来说说线程吧:
-
线程的生命周期:
-
新建状态:创建对象
当线程对象对创建后,即进入了新建状态,如:Thread thread = new MyThread(); - 就绪状态:调用star()方法
-
运行状态:线程开始执行
当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态 -
阻塞状态:线程暂停
暂停:sleep(time)方法
等待:wait()方法
唤醒:notify()方法 -
死亡状态:线程执行完毕,被停止或因异常退出
停止:stop()方法
-
新建状态:创建对象
-
线程的创建:
- 继承Thread类
- 实现Runnable接口
我们来用一下继承Thread类的方法:
- class Demo extends Thread{//创建线程类(继承了Thread类)
- private String name;//参数
- public Demo(String name) {//构造方法
- this.name=name;
- }
- public void run(){//启动方法
- for (int i = 1; i <= 10; i++) {
- System.out.println(name+"第"+i+"次运行");//测试方法
- }
- }
- }
- public class Test {
- public static void main(String[] args) throws IOException {
- new Demo("大妖怪").start();//实例化第一个线程类,传入参数“大妖怪”并启动该线程
- new Demo("小妖怪").start();//实例化第二个线程类,传入参数“小妖怪”并启动该线程
- }
- }
接下来我们来试一下实现Runnable接口的方法:
- class Demo implements Runnable{
- private String name;//参数
- public Demo(String name){//构造方法
- this.name = name;
- }
- @Override
- public void run() {//启动方法(该方法为Runnable接口内的抽象方法,必须重写)
- for (int i = 1; i <= 10; i++) {
- System.out.println(name+"第"+i+"次运行");//测试方法
- }
- }
- }
- public class Test {
- public static void main(String[] args){
- Demo demo1 = new Demo("大妖怪");//实例化一个Demo类demo1,传入参数“大妖怪”
- Demo demo2 = new Demo("小妖怪");//实例化一个Demo类demo2,传入参数“小妖怪”
- Thread thread1 = new Thread(demo1);//创建一个线程thread1,并把demo1传入其中
- Thread thread2 = new Thread(demo2);//创建一个线程thread2,并把demo2传入其中
- thread1.start();//启动thread1线程
- thread2.start();//启动thread2线程
- }
- }
以上两种方法实现的结果是一样的,就是开两个线程,分别打印十次“大妖怪”和十次“小妖怪”。 当然,多运行几次之后你会发现,每次运行的顺序都是不一样的的,这是因为同时开启了两个线程,访问的也是两个不同的run()方法,但是我们输出的资源(控制台)只有一个,于是每次输出,两个run()方法就会开始进行抢夺控制台大战(相当于是线程之间的抢夺战),每一轮谁抢赢了,控制台就给谁用,直到两个进程都结束。
那么有没有一种方法可以让一个线程执行完了再执行另一个线程呢?当然有:同步锁(synchronized关键字)
-
synchronized
- class Demo implements Runnable{
- @Override
- public void run() {//启动方法(该方法为Runnable接口内的抽象方法,必须重写)
- synchronized (this) {
- for (int i = 1; i <= 10; i++) {
- System.out.println(Thread.currentThread().getName()+"第"+i+"次运行");//测试方法(Thread.currentThread().getName()获取线程名)
- }
- }
- }
- }
- public class Test {
- public static void main(String[] args) {
- Demo demo = new Demo();//实例化一个Demo类demo
- Thread thread1 = new Thread(demo,"线程1");//创建一个线程thread1,并把demo传入其中并起名为"线程1"
- Thread thread2 = new Thread(demo,"线程2");//创建一个线程thread2,并把demo传入其中并起名为"线程2"
- thread1.start();//启动thread1线程
- thread2.start();//启动thread2线程
- }
- }
本段代码运行的结果为
在以上代码中,我们只实例化了一个Demo,这样,我们两个线程在访问demo下的run()方法时就是访问的同一个run()方法了,那么我们用synchronized代码块包住要执行的代码,就可以在两个线程中锁住这段代码了。这个锁住的意思就是,当一个线程在访问这段代码时,别的试图访问这段代码的程序就只有等访问的线程执行完才能得到访问权,而且在等待过程中,每个线程都会不断尝试去活动被锁住区域的访问权。这里好比被锁的方法是一个房间,线程是一个个人,在没锁之前,所有人都可以进入这个房间,当我们加锁之后,会给出一把钥匙,每个线程(人)会去竞争这把钥匙,谁得到了钥匙就可以进入房间(访问代码),然后它出来之后,会把钥匙释放出来,剩下的线程继续竞争,直到全部线程都执行完。
- synchronized修饰符可以修饰方法,类,代码块。被修饰的内容一次只能被一个线程访问。
-
同步锁的优点是
- 可以使多线程程序运行的结果可控制
- 可以保护共享数据(比如当多个线程对同一个数据进行操作时)
-
同步锁的缺点是
- 当一个线程运行的时候阻塞了其它线程,如果使程序执行效率变慢
- 当一个线程运行的时候,其它线程一直在处于尝试获取访问权的状态,十分消耗资源