一、简介
1、什么是线程?
要解释线程,就必须明白什么是进程。
2、什么是进程呢?
进程是指运行中的应用程序,每个进程都有自己独立的地址空间(内存空间),比如用户点击桌面的IE浏览器,就启动了一个进程,操作系统就会为该进程分配独立的地址空间。
要点:用户每启动一个进程,操作系统就会为该进程分配一个独立的内存空间。
二、线程--概念
在明白进程后,就比较容易理解线程的概念。
1、什么是线程呢?
是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。同一进程中的多个线程之间可以并发执行。
写的太官方了,我的理解就是:
进程就类似一个班级,线程就类似这个班级里面的小学生,多个小学生可以同时做一件事,也可以排队等前面一个小学生做完(synchronized,同步锁操作);
可以让出当前的空间(yield),让另外一个小学生做,如果别人竞争不到或者没有人跟你竞争,那就自己继续干活;
也可以适当的休息一段时间(sleep),让另外一个小学生干活,之后再加入;
如果有两个要好的朋友,一个做完活了,要走了,你想让他等你,你可以用(join)让他等你做完活;
看过一个总结的特别简单的句子(进程就是打开一个APP,线程就是APP里面的各种操作)
2、线程的类型
线程中有两种类型,用户线程和守护线程
用户线程:运行在前台,执行具体的任务,程序的主线程,连接网络的子线程都是用户线程
守护线程:运行在后台,为其他前台线程服务
守护线程的特点:一旦所有的用户线程都结束运行,守护线程随着用户线程的结束而结束
(简单理解:用户线程就好比公司里面的CEO,经理这些,守护线程就好比保安,CEO,经理都下班了,保安也跟着下班呗)
设置线程成为守护线程(Daemon)
Thread thread = new Thread();
thread.setDaemon(true); // 调用对象的setDaemon方法,设置为守护线程,必须在start()前
thread.start();
三、Thread类和Runnable接口
如果想要在main方法中,定义多个线程去执行一段代码,java语言中实现了多线程编程的类和接口,在java.lang包中定义了Thread类和Runnable接口。
Thread类实现了Runnanble接口。Runnable对象为可运行对象,一个线程的执行是执行该对象里面的run方法
Thread的构造方法如下:
public Thread()
public Thread(Runnable target)
public Thread(String name)
public Thread(Runnable target, String name)
public Thread(ThreadGroup group, Runnable target)
public Thread(ThreadGroup group, String name)
public Thread(ThreadGroup group, Runnable target, String name)
/**
target:为线程的目标对象,即线程调用start()方法启动后运行那个对象的run()方法
name: 为该线程的名称
group:指定该线程属于哪一个组
*/
Thread的常用方法有:
public static Thread currentThread(); // 返回当前正在执行的线程对象
public void getName();// 返回当前的线程名称
public static void sleep(long millis); // 让当前线程休眠一段时间,让出当前CPU
public static void yield(); // 让出当前CPU,如果存在线程等待并且优先级别高的情况,则该线程获取到CPU执行空间;如果存在同等线程级别的情况,有可能当前让出的线程继续抢到CPU执行空间
public void run(); // 线程的线程体,方法都是写在这里面
public void start(); //JVM调用线程的run方法,启动线程开始
public void setDaemon(boolean on); // 设置线程为守护线程
四、线程的创建
创建线程有两种方法:
1、继承Thread并覆盖run方法
2、实现Runnable接口并重写run方法
继承Thread方法
简单做一个输出
public class ThreadA extends Thread {
private String name;
public ThreadA(String name){
super(name);
this.name = name;
}
@Override
public void run(){
System.out.println(Thread.currentThread().getName() + " 线程开始执行 ");
for (int i = 0; i < 5; i++) {
System.out.println("子线程 "+name+"运行:"+i);
try{
sleep((int)Math.random()*10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.printf(Thread.currentThread().getName()+" 线程运行结束 ");
}
}
main方法执行
public static void main(String [] args){
System.out.println(Thread.currentThread().getName()+"主线程开始运行");
ThreadA threadA = new ThreadA("A");
ThreadA threadA1 = new ThreadA("B");
threadA.start();
threadA1.start();
System.out.println(Thread.currentThread().getName()+"主线程结束执行");
}
/**
执行结果:
main主线程开始运行
main主线程结束执行
A 线程开始执行
子线程 A运行:0
B 线程开始执行
子线程 B运行:0
子线程 A运行:1
子线程 A运行:2
子线程 A运行:3
子线程 A运行:4
A 线程运行结束
子线程 B运行:1
子线程 B运行:2
子线程 B运行:3
子线程 B运行:4
B 线程运行结束
*/
这里可以看出,两个线程互相抢占资源,输出的信息,都是乱的;再看main主线程,一开始就结束了运行,可以看出我们的子线程是执行成功的;
如果我们想要主线程等待子线程执行成功之后,再做下一步操作呢?可以用Join() 方法
public static void main(String[] args){
System.out.println(Thread.currentThread().getName()+"主线程开始运行");
ThreadA threadA = new ThreadA("A");
ThreadA threadA1 = new ThreadA("B");
threadA.start();
threadA1.start();
// join的意思:等待指定线程终止,这里可以理解为: main主线程等待子线程的终止
threadA.join();
threadA1.join();
System.out.println(Thread.currentThread().getName()+"主线程结束执行");
}
/**
main主线程开始运行
A 线程开始执行
子线程 A运行:0
B 线程开始执行
子线程 B运行:0
子线程 A运行:1
子线程 A运行:2
子线程 A运行:3
子线程 A运行:4
A 线程运行结束
子线程 B运行:1
子线程 B运行:2
子线程 B运行:3
子线程 B运行:4
B 线程运行结束
main主线程结束执行
*/
仔细看输出日志,main主线程结束执行排在最后面;
看的这个排序是在不爽,我们想要让A输出完,B再输出的话,怎么处理呢?
用synchronized 同步锁,他的意思是:被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
@Override
public synchronized void run(){
System.out.println(Thread.currentThread().getName() + " 线程开始执行 ");
for (int i = 0; i < 5; i++) {
System.out.println("子线程 "+name+"运行:"+i);
try{
sleep((int)Math.random()*10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" 线程运行结束 ");
}
/**
执行结果:
main主线程开始运行
B 线程开始执行
子线程 B运行:0
子线程 B运行:1
子线程 B运行:2
子线程 B运行:3
子线程 B运行:4
B 线程运行结束
A 线程开始执行
子线程 A运行:0
子线程 A运行:1
子线程 A运行:2
子线程 A运行:3
子线程 A运行:4
A 线程运行结束
main主线程结束执行
*/
嗯,很奈斯
使用Synchronized 表示多个线程同时调用一个方法的时候,没有调用wait()或者sleep()方法的话,只能先进入的一个线程执行完毕之后,后一个线程才能进入。
实现Runnable接口
实现两个线程执行同一个方法,打印输出10、9、8...
// 多个线程,简单输出10、9、8...
public class RunnableA implements Runnable {
private int count = 10;
@Override
public void run() {
while (count > 0){
System.out.println("当前线程名称是 >>> "+Thread.currentThread().getName()+" ,count为 >>> "+count);
count--;
}
}
}
调用方法
public static void main(String[] args){
RunnableA ra = new RunnableA();
Thread tA = new Thread(ra,"A");
Thread tB = new Thread(ra,"B");
tA.start();
tB.start();
}
/**
执行结果:
当前线程名称是 >>> A ,count为 >>> 10
当前线程名称是 >>> B ,count为 >>> 10
当前线程名称是 >>> B ,count为 >>> 8
当前线程名称是 >>> A ,count为 >>> 9
当前线程名称是 >>> B ,count为 >>> 7
当前线程名称是 >>> A ,count为 >>> 6
当前线程名称是 >>> B ,count为 >>> 5
当前线程名称是 >>> A ,count为 >>> 4
当前线程名称是 >>> A ,count为 >>> 2
当前线程名称是 >>> A ,count为 >>> 1
当前线程名称是 >>> B ,count为 >>> 3
*/
可以看出,两个线程同时执行一个方法,得出的结果存在两个10的情况,我们可以加synchornized 同步锁去处理,但是这样,出现的情况就是要么 tA全执行完,要么tB全执行完,两个线程变成了单个线程去使用。
如果我们想要 tA线程输出双数,tB线程输出单数的情况,应该怎么处理呢?
嘿,上代码
// 多个线程,简单输出10、9、8...
public class RunnableA implements Runnable {
private int count = 10;
private int i = 0;
@Override
public void run() {
// synchronized (this) 表示的是该代码块同步,this表示的是当前进来的线程。
synchronized (this) {
while (count > 0) {
notify(); // 唤醒当前进来的线程,如果没有在等待状态,也可以唤醒
System.out.println("当前线程名称是 >>> " + Thread.currentThread().getName() + " ,count为 >>> " + count);
count--;
try {
// wait:使当前线程进入等待状态,并且释放当前对象所持有的锁
// (简单理解就是 synchronized 锁住的对象,有方法,有变量,简单理解为当前线程释放了对资源的控制)
wait();
// System.out.println(Thread.currentThread().getName() + ">>>" + Thread.currentThread().isAlive()+" >>> "+(i++));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
调用方法
public static void main(String[] args){
RunnableA ra = new RunnableA();
Thread tA = new Thread(ra,"A");
Thread tB = new Thread(ra,"B");
tA.start();
tB.start();
}
/**
执行结果:
当前线程名称是 >>> A ,count为 >>> 10
当前线程名称是 >>> B ,count为 >>> 9
当前线程名称是 >>> A ,count为 >>> 8
当前线程名称是 >>> B ,count为 >>> 7
当前线程名称是 >>> A ,count为 >>> 6
当前线程名称是 >>> B ,count为 >>> 5
当前线程名称是 >>> A ,count为 >>> 4
当前线程名称是 >>> B ,count为 >>> 3
当前线程名称是 >>> A ,count为 >>> 2
当前线程名称是 >>> B ,count为 >>> 1
*/
【注:博主一直看不懂 wait() 后面说的,并且释放该线程所持有的锁,后面捋了一遍,我的简单粗暴的理解就是:好比有一个屋子,里面很多吃的,一群饿鬼在门外面抢着要进去吃东西,你抢先进去了,然后马上把门关上了,等你吃的差不多了,就要把门打开了。不能这么自私吧。栗子不是很恰当,反正意思就是说,一个线程使用wait()进入等待状态的时候,同时也会释放掉对当前锁的控制,让其他线程可以抢占到锁】
总结:
实现Runnable接口比继承Thread具有的优势:
1、避免java中单继承的限制
2、适合多个相同程序的代码去实现同一个资源
3、增强代码的健壮性,代码可以被多个线程共享,代码和数据独立
五、synchronized线程同步
synchronized是java中的关键字,是一种同步锁,修饰的对象有几种
1、修饰一个代码块,被修饰的代码块称为同步语句块,作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码的对象
2、修饰一个类,其作用的范围是synchronized的括号{} 括起来的代码,作用的对象是这个类的所有对象
3、修饰一个方法,被修饰的方法称为同步方法,作用的范围是整个方法,作用的对象是调用这个方法的对象
4、修饰一个静态的方法,作用的范围是整个静态方法,作用的对象是这个类的所有对象
上栗子
synchronized修饰代码块
public void run(){
synchronized(this){
System.out.println("修饰一个代码块,作用的对象是调用这个代码块的对象");
}
}
当两个并发线程同时访问这个代码块的时候,同一时刻只能有一个线程得到执行,另外一个阻塞。必须等到当前线程执行完毕之后才能执行该代码块。
我们也可以通过定义特殊变量来充当锁
private byte[] lock = new byte[0]; // 特殊的instance变量
public void method()
{
synchronized(lock) {
// todo 同步代码块
}
}
说明:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。
synchronized修饰方法
public synchronized void run(){
System.out.println("修饰方法范围是整个函数");
}
这样的写法相当于
public synchronized void methodName(){
}
public void run(){
synchronized(this){
}
}
synchronized关键字不能继承。
虽然可以使用synchronized来定义方法,但synchronized并不属于方法定义的一部分,因此,synchronized关键字不能被继承。如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。
synchronized修饰静态方法
public synchronized static void methodName(){
System.out.println("锁定的是这个类的所有对象");
}
synchronized修饰一个类
public static void method() {
synchronized(ClassName.class) {
System.out.println("所有对象用的是同一把锁");
}
}
总结:
只是单纯做一个笔记总结,期间拷贝一些博主的代码,方便自己日后使用记录。
2019第二天的一个感悟就是:不能活在舒适区啊,尤其是我们程序猿,要时刻有危机感。谨记自己!!!
参考网址链接以下
https://blog.csdn.net/luoweifu/article/details/46595285 (编程思想之多线程与多进程)
https://blog.csdn.net/luoweifu/article/details/46613015#t3 (Java中Synchronized的用法)
https://www.cnblogs.com/GarfieldEr007/p/5746362.html (Java多线程学习(吐血超详细总结))