Java学习笔记之多线程方面

并发与并行

并发:两个或多个事件在同一时间段内发生。
并行:两个或多个事件在同一时刻发生(同时发生)。

进程与线程

进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,-一个进程中至少有一 个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
简而言之: 一个程序运行后至少有一个进程, 一个进程中可以包含多个线程

多线程好处:效率高、多个线程之间互不影响。

线程调度

分时调度:所有线程轮流使用CPU使用权,怕平均分配每个线程占用CPU的时间。
抢占式调度:优先让优先级高的线程使用CPU,如果线程的优先级相同,就随机选择一个(线程随机性),Java为抢占式调度

Java线程随机性

创建多线程

继承Thread类
1、创建一个类继承Thread类
2、重写Thread中的run()方法
3、创建类的对象,再调用start方法。
一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例。例如,计算大于某一规定值的质数的线程可以写成:

 class PrimeThread extends Thread {
         long minPrime;
         PrimeThread(long minPrime) {
             this.minPrime = minPrime;
         }
 
         public void run() {
             // compute primes larger than minPrime
              . . .
         }
     }

然后,下列代码会创建并启动一个线程:

 PrimeThread p = new PrimeThread(143);
     p.start();
//创建一个类继承Thread类
public class NewThread extends Thread{
    @Override
    //重写Thread中的run()方法
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("run()方法"+i);
        }
    }
}

public class NewMain {
    public static void main(String[] args) {
        //创建类的对象,再调用start方法。
        NewThread newThread = new NewThread();
        newThread.start();

        for (int i = 0; i < 5; i++) {
            System.out.println("main()方法"+i);
        }
    }
}

两次执行结果:

"E:\JAVA\IntelliJ IDEA 2019.3.2\jbr\bin\java.exe" "-javaagent:E:\JAVA\IntelliJ IDEA 2019.3.2\lib\idea_rt.jar=61793:E:\JAVA\IntelliJ IDEA 2019.3.2\bin" -Dfile.encoding=UTF-8 -classpath E:\JAVA\project\day06-code\out\production\day06-code com.lg.thread.NewMain
main()方法0
run()方法0
main()方法1
run()方法1
main()方法2
run()方法2
main()方法3
run()方法3
main()方法4
run()方法4

Process finished with exit code 0

"E:\JAVA\IntelliJ IDEA 2019.3.2\jbr\bin\java.exe" "-javaagent:E:\JAVA\IntelliJ IDEA 2019.3.2\lib\idea_rt.jar=61947:E:\JAVA\IntelliJ IDEA 2019.3.2\bin" -Dfile.encoding=UTF-8 -classpath E:\JAVA\project\day06-code\out\production\day06-code com.lg.thread.NewMain
run()方法0
run()方法1
run()方法2
run()方法3
run()方法4
main()方法0
main()方法1
main()方法2
main()方法3
main()方法4

Process finished with exit code 0

随机打印结果原理
在这里插入图片描述
内存图
在这里插入图片描述

获取当前正在执行线程名的方法重写run()方法的过程中。

public class TestThread extends Thread {
    @Override
    public void run() {
    	//方法1
        /*String str = getName();
        System.out.println(str);*/
        //方法2
        /*Thread t = Thread.currentThread();
        System.out.println(t);*/
        //链式编程
        System.out.println(Thread.currentThread().getName());
    }
}
public class Test {
    public static void main(String[] args) {
        TestThread th = new TestThread();
        th.start();
        new TestThread().start();
        new TestThread().start();
        System.out.println(Thread.currentThread().getName());//mian.获取主线程名称
    }
}

结果不一:

main
Thread-0
Thread-1
Thread-2

改变线程名程两种方法

public class TestThread extends Thread {
    public TestThread() {
    }
    //创建带参数的构造方法。把线程名传递给父类,让父类Thread给线程改名
    public TestThread(String name) {
        super(name);
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

public class Test {
    public static void main(String[] args) {
        TestThread th = new TestThread();
        //使用Thread类中的setName()方法
        th.setName("One");
        th.start();
        //创建带参数的构造方法。把线程名传递给父类,让父类Thread给线程改名
        new TestThread("Two").start();
    }
}

线程睡眠使得线程以多少毫秒睡眠。

public class TestSleep {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 5; i++) {
            Thread.sleep(1000);//使得线程睡眠一秒,结果每个一秒打印一个数
            System.out.println(i);
        }
    }
}

创建线程的第二种方法
创建线程的另一种方法是声明实现 Runnable 接口的类。该类然后实现 run 方法。然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。

 class PrimeRun implements Runnable {
         long minPrime;
         PrimeRun(long minPrime) {
             this.minPrime = minPrime;
         }
 
         public void run() {
             // compute primes larger than minPrime
              . . .
         }
     }

然后,下列代码会创建并启动一个线程:

 PrimeRun p = new PrimeRun(143);
     new Thread(p).start();

具体代码

public class Test implements Runnable {
	//重写Runnable的run()方法
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("新线程"+i);
        }
    }
}

public class New {
    public static void main(String[] args) {
        Test test = new Test();
        //开启线程
        new Thread(test).start();
        for (int i = 0; i < 5; i++) {
            System.out.println("main线程"+i);
        }
    }
}

结果不一:

"E:\JAVA\IntelliJ IDEA 2019.3.2\jbr\bin\java.exe" "-javaagent:E:\JAVA\IntelliJ IDEA 2019.3.2\lib\idea_rt.jar=63372:E:\JAVA\IntelliJ IDEA 2019.3.2\bin" -Dfile.encoding=UTF-8 -classpath E:\JAVA\project\day06-code\out\production\day06-code com.ls.demo02.New
main线程0
新线程0
main线程1
新线程1
main线程2
新线程2
main线程3
新线程3
main线程4
新线程4

Process finished with exit code 0

"E:\JAVA\IntelliJ IDEA 2019.3.2\jbr\bin\java.exe" "-javaagent:E:\JAVA\IntelliJ IDEA 2019.3.2\lib\idea_rt.jar=63405:E:\JAVA\IntelliJ IDEA 2019.3.2\bin" -Dfile.encoding=UTF-8 -classpath E:\JAVA\project\day06-code\out\production\day06-code com.ls.demo02.New
main线程0
新线程0
main线程1
新线程1
新线程2
新线程3
新线程4
main线程2
main线程3
main线程4

Thread和Runnable的区别

1、避免单继承的局限性。一个类只能继承一个类,类继承了Thread类就不能继承其他类。实现了Runnable接口,还可以继承其他类,实现其他接口。
2、增强了程序的扩展性,降低了程序的耦合性(解耦)。实现Runnable方式,把设置线程任务和开启新线程进行了分离。(实现类中重写Run方法:用来是设置线程任务。创建Thread类调用start方法:用来开启线程)
https://blog.csdn.net/dfshsdr/article/details/92432519
匿名内部类方式实现线程创建
简化代码
1、把子类继承父类,重写父类方法,创建子类对象一步完成
2、把实现类实现接口,重写接口中的方法,创建实现类对象合成一步。
产物子类或实现类对象,而这个类没有名字
具体代码:

public class New {
    public static void main(String[] args) {
    //线程的接口Runnable
       Runnable r =  new Runnable(){
            @Override
            public void run() {
                for (int s = 0; s < 2; s++) {
                    System.out.println(Thread.currentThread().getName()+"Runnable");
                }
            }
        };
        //线程的父类Thread
       new Thread(r).start();
        new Thread(){
            @Override
            public void run() {
                for (int s = 0; s < 2; s++) {
                    System.out.println(Thread.currentThread().getName()+"Thread");
                }
            }
        }.start();
        //简化接口,开始套娃
	   new Thread(new Runnable(){
            @Override
            public void run() {
                for (int s = 0; s < 2; s++) {
                    System.out.println(Thread.currentThread().getName()+"套娃");
                }
            }
        }).start();
    }
}

结果依旧不一:

"E:\JAVA\IntelliJ IDEA 2019.3.2\jbr\bin\java.exe" "-javaagent:E:\JAVA\IntelliJ IDEA 2019.3.2\lib\idea_rt.jar=64545:E:\JAVA\IntelliJ IDEA 2019.3.2\bin" -Dfile.encoding=UTF-8 -classpath E:\JAVA\project\day06-code\out\production\day06-code com.ls.demo02.New
Thread-2套娃
Thread-2套娃
Thread-1Thread
Thread-1Thread
Thread-0Runnable
Thread-0Runnable

Process finished with exit code 0

"E:\JAVA\IntelliJ IDEA 2019.3.2\jbr\bin\java.exe" "-javaagent:E:\JAVA\IntelliJ IDEA 2019.3.2\lib\idea_rt.jar=64552:E:\JAVA\IntelliJ IDEA 2019.3.2\bin" -Dfile.encoding=UTF-8 -classpath E:\JAVA\project\day06-code\out\production\day06-code com.ls.demo02.New
Thread-0Runnable
Thread-0Runnable
Thread-2套娃
Thread-2套娃
Thread-1Thread
Thread-1Thread

Process finished with exit code 0

线程安全

多个线程访问共享数据。

public class ThreadSafe implements Runnable {
    private int ticket = 10;
    @Override
    public void run() {
        while(true){
            if(ticket>0){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程"+Thread.currentThread().getName()+"正在卖第"+ticket+"票");
                ticket--;
            }
        }
    }
}
public class Demo {
    public static void main(String[] args) {
        ThreadSafe th = new ThreadSafe();
        Thread th0 = new Thread(th);
        Thread th1 = new Thread(th);
        Thread th2 = new Thread(th);
        th0.start();
        th1.start();
        th2.start();
    }
}

结果:

线程Thread-0正在卖第10票
线程Thread-2正在卖第10票
线程Thread-1正在卖第10票
线程Thread-2正在卖第7票
线程Thread-0正在卖第7票
线程Thread-1正在卖第5票
线程Thread-0正在卖第4票
线程Thread-2正在卖第4票
线程Thread-1正在卖第2票
线程Thread-2正在卖第1票
线程Thread-0正在卖第1票
线程Thread-1正在卖第-1

线程安全产生的原理
在这里插入图片描述
注意:线程安全是不能产生的,可以让一个线程在访问共享资源时,无论是否失去CPU的执行权,其他的线程只能等待,等待当前线程使用完共享资源,其他线程才可以使用资源。

解决线程安全问题:
1、同步代码块:synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实施互斥访问。

synchronized(同步锁){
	需要同步操作的代码
}

同步锁:
对象的同步锁只是一个概念,可以想象为再对象上标记了一个锁
1、锁对象可以是任意类型。
2、多个线程对象,需要使用同一把锁。
在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他线程只能在外等着。

public class ThreadSafe implements Runnable {
    private int ticket = 10;
    Object obj = new Object();
    @Override
    public void run() {
        while(true){
            synchronized (obj){
                if(ticket>0){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程"+Thread.currentThread().getName()+"正在卖第"+ticket+"票");
                    ticket--;
                }
            }
        }
    }
}

public class Demo {
    public static void main(String[] args) {
        ThreadSafe th = new ThreadSafe();
        Thread th0 = new Thread(th);
        Thread th1 = new Thread(th);
        Thread th2 = new Thread(th);
        th0.start();
        th1.start();
        th2.start();
    }
}

结果:

"E:\JAVA\IntelliJ IDEA 2019.3.2\jbr\bin\java.exe" "-javaagent:E:\JAVA\IntelliJ IDEA 2019.3.2\lib\idea_rt.jar=59831:E:\JAVA\IntelliJ IDEA 2019.3.2\bin" -Dfile.encoding=UTF-8 -classpath E:\JAVA\project\day06-code\out\production\day06-code com.ls.demo02.Demo
线程Thread-0正在卖第10票
线程Thread-0正在卖第9票
线程Thread-0正在卖第8票
线程Thread-0正在卖第7票
线程Thread-0正在卖第6票
线程Thread-0正在卖第5票
线程Thread-2正在卖第4票
线程Thread-1正在卖第3票
线程Thread-1正在卖第2票
线程Thread-2正在卖第1

解析:
在这里插入图片描述
2、同步方法
synchronized修饰的方法叫做同步方法,保证A线程执行的时候,其他线程只能在方法外面等着。

public synchronized void method(){
	可能产生线程安全的代码
}

定义同步方法,同步方法会把方法内部的代码锁住,只让一个线程执行。非静态同步方法的锁对象是:就是实现类对象new ThreadSafe();也就是this静态方法的锁对象:不能是this,this是创建对象之后产生的,静态方法优先于对象,静态同步方法的锁对象是本类的class属性-->class文件属性。

//静态同步方法
    public static /*synchronized */ void staticMethod()
    {
        synchronized (ThreadSafe.class){//《-----------同样保证线程安全
            if(ticket0>0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程"+Thread.currentThread().getName()+"正在卖第"+ticket0+"票");
                ticket0--;
            }
        }
            
    }

同步方法具体代码:

public class ThreadSafe implements Runnable {
    private int ticket = 10;
    private static int ticket0 = 10;
    Object obj = new Object();
    @Override
    public void run() {
        System.out.println("this"+this);
        while(true)
        {
            //method();//调用非静态同步方法
            staticMethod();//调用静态同步方法
        }
    }
    //非静态同步方法
    public synchronized void method()
    {
        synchronized (obj){
            if(ticket>0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程"+Thread.currentThread().getName()+"正在卖第"+ticket+"票");
                ticket--;
            }
        }

    }
    //静态同步方法
    public static synchronized void staticMethod()
    {

            if(ticket0>0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程"+Thread.currentThread().getName()+"正在卖第"+ticket0+"票");
                ticket0--;
            }
    }
}

结果未出现线程安全问题:

runcom.ls.ThreadSafe@312b1dae
thiscom.ls.ThreadSafe@312b1dae
thiscom.ls.ThreadSafe@312b1dae
thiscom.ls.ThreadSafe@312b1dae
线程Thread-0正在卖第10票
线程Thread-0正在卖第9票
线程Thread-2正在卖第8票
线程Thread-2正在卖第7票
线程Thread-2正在卖第6票
线程Thread-2正在卖第5票
线程Thread-1正在卖第4票
线程Thread-2正在卖第3票
线程Thread-0正在卖第2票
线程Thread-0正L在卖第1

3、使用Lock锁
public interface LockLock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。

锁是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问。一次只能有一个线程获得锁,对共享资源的所有访问都需要首先获得锁。不过,某些锁可能允许对共享资源并发访问,如 ReadWriteLock 的读取锁。

synchronized 方法或语句的使用提供了对与每个对象相关的隐式监视器锁的访问,但却强制所有锁获取和释放均要出现在一个块结构中:当获取了多个锁时,它们必须以相反的顺序释放,且必须在与所有锁被获取时相同的词法范围内释放所有锁。

虽然 synchronized 方法和语句的范围机制使得使用监视器锁编程方便了很多,而且还帮助避免了很多涉及到锁的常见编程错误,但有时也需要以更为灵活的方式使用锁。例如,某些遍历并发访问的数据结果的算法要求使用 “hand-over-hand” 或 “chain locking”:获取节点 A 的锁,然后再获取节点 B 的锁,然后释放 A 并获取 C,然后释放 B 并获取 D,依此类推。Lock 接口的实现允许锁在不同的作用范围内获取和释放,并允许以任何顺序获取和释放多个锁,从而支持使用这种技术。

随着灵活性的增加,也带来了更多的责任。不使用块结构锁就失去了使用 synchronized 方法和语句时会出现的锁自动释放功能。在大多数情况下,应该使用以下语句:

 Lock l = ...; 
     l.lock();
     try {
         // access the resource protected by this lock
     } finally {
         l.unlock();//无论代码是否有异常,都将锁释放掉。
     }

锁定和取消锁定出现在不同作用范围中时,必须谨慎地确保保持锁定时所执行的所有代码用 try-finally 或 try-catch 加以保护,以确保在必要时释放锁。

具体代码:

public class RunnableImpl implements Runnable {
    public int ticket = 10;
    //创建一个ReentrantLock对象
    Lock l = new ReentrantLock();
    @Override
    public void run() {
        while(true)
        {
            method();//改动后
            //methodOne();//改动前
        }
    }
    public void method(){
        //在可能要出现安全问题的代码前调用lock接口中的方法lock获取锁
        l.lock();
        try {
            Thread.sleep(100);
            if(ticket>0){
                System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
                ticket--;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            //在可能要出现安全问题的代码后调用lock接口中的方法lock释放锁
            l.unlock();//不管程序是否发生异常都要释放锁
        }
    }
    public void methodOne(){
        l.lock();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if(ticket>0){
            System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
            ticket--;
        }
        l.unlock();
    }
}

public class Test {
    public static void main(String[] args) {
        RunnableImpl run = new RunnableImpl();
        Thread one = new Thread(run);
        Thread two = new Thread(run);
        Thread three = new Thread(run);
        one.start();
        two.start();
        three.start();
    }
}

结果如下:

"E:\JAVA\IntelliJ IDEA 2019.3.2\jbr\bin\java.exe" "-javaagent:E:\JAVA\IntelliJ IDEA 2019.3.2\lib\idea_rt.jar=64755:E:\JAVA\IntelliJ IDEA 2019.3.2\bin" -Dfile.encoding=UTF-8 -classpath E:\JAVA\project\day06-code\out\production\day06-code com.ls.demo03.Test
Thread-0正在卖第10张票
Thread-0正在卖第9张票
Thread-2正在卖第8张票
Thread-1正在卖第7张票
Thread-0正在卖第6张票
Thread-2正在卖第5张票
Thread-2正在卖第4张票
Thread-1正在卖第3张票
Thread-0正在卖第2张票
Thread-2正在卖第1张票

线程的状态

等待唤醒案例,线程之间的通信。

public class Test {
    public static void main(String[] args) {
        Object obj = new Object();

        new Thread(){
            @Override
            public void run() {
                System.out.println("告知包子数量");
                synchronized (obj){
                    //此处异常不能抛出
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("包子做好了,开吃!");
                }

            }
        }.start();
        new Thread(){
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (obj){
                    System.out.println("包子做好了");
                    obj.notify();
                }
            }
        }.start();
    }
}

本人意在记录自己的学习,并无他心

发布了22 篇原创文章 · 获赞 0 · 访问量 430

猜你喜欢

转载自blog.csdn.net/ENDEAVOR__/article/details/105321904