Runnable和Thread:
线程使用来执行任务的,任务的代码是在run方法写的,run()方法在Runnable接口里。
Runnable实现多线程:
让自己写的类实现Runnable接口,然后在类里重写Runnable的run()方法,run()其实就是线程要执行的任务。
接着就这 mian()方法里创建一个线程,让线程去执行run()里的任务。任务在类里,所以要实例化该类的一个对象,然后用该对象作为Thread(object)的参数,创建多线程。
public static void main(String[] args) {
MyThread2 myThread2=new MyThread2();//run()方法在MyThread2里,所以要实例化一个MyThread2的的对象
Thread thread=new Thread(myThread2);//创建一个线程,用来执行myThread2.run()。
thread.start();//线程启动
}
public class MyThread2 implements Runnable {
private int num=5;/**
* 实现Runnable的run()方法
*/
@Override
public void run() {
while (num>=0) {
System.out.println(num);
num--;
}
}
}
Thread实现多线程:
从源码可知,Thread类实现了Runble接口,我们的类有继承了Thread,所以我们的写的类实例化的对象有run()方法(线程要执行的任务)和线程启动的方法start()。因此直接实例化一个自己写的类的对象,然后启动就行。
public class MyThread extends Thread{
@Override
public void run(){
long BeginTime=System.currentTimeMillis();
for(int i=0;i<100000;i++)
{
Thread.yield();
System.out.println(i);
}
long EndTime=System.currentTimeMillis();
System.out.println("用时:"+(EndTime-BeginTime));
}
}
public static void main(String[] args) {
MyThread myThread=new MyThread();//实例化一个对象,这个对象有rnu()方法(因为MyThread继承了THread,Thread实现了Runnable接口)
myThread.start();//myThread类调用了Thread的start()方法来启动线程。
}
实现Runnable接口的对象不是线程,只是个任务,Thread才是真正的线程创建者。说明任务和线程是分开的,任务放在线程里才被执行。
start()和run()区别:
start()是线程的启动者
run()是线程任务的运行者
线程休眠(sleep):
线程休眠,使线程处于阻塞状态,
例如Thread.sleep(1000),是将线程处于阻塞状态1000毫秒,期间该线程不会获得执行的机会,1000毫秒后再将该线程移动至就绪队列,等待操作系统的调度。
线程让步(yield):
将线程转入就绪状态,yeild()只是让线程暂停一下,等待操作系统的下一次的调度。当末县春城调用了yield()后,只有优先级与当前进程相同,或者更高的,处于就绪状态的线程才会获得执行机会。
synchronized:
出现线程安全的原因:主要来源于JMM的设计,主要集中在主内存和线程的工作内存而导致的内存可见性问题,以及重排序导致的问题。
使用线程同步的方式来实现是线程安全。非线程安全问题存在实例变量中。对于方法内部的私有变量不存在线程安全问题。
同步:一次只让一个线程执行任务,在一个线程执行人任务时,给这个任务上锁,知道这个线程执行完这个任务,才解锁然后让另外一个线程执行任务。(即确保线程互斥第访问同步代码),让每一个线程依次去读写共享变量。
synchronized锁住的是括号里的对象,而不是代码。对于非static的synchronized方法,锁的就是对象本身也就是this。
对于要执行的任务:
package com.company;
public class Main {
public static void main(String[] args) {
/**
* 三个线程使用同一个对象syn
* 所以只要锁住syn的test方法就能实现同步
* 或者在test()方法里使用synchronized(this)
*/
/* Syn syn=new Syn();
MyThread myThread=new MyThread(syn);
new Thread(myThread,"A").start();
new Thread(myThread,"B").start();
new Thread(myThread,"C").start();*/
Syn syn1=new Syn();
Syn syn2=new Syn();
Syn syn3=new Syn();
MyThread myThread1=new MyThread(syn1);
MyThread myThread2=new MyThread(syn2);
MyThread myThread3=new MyThread(syn3);
new Thread(myThread1,"A").start();
new Thread(myThread1,"C").start();
new Thread(myThread1,"C").start();
}
}
package com.company;
public class MyThread implements Runnable{
private Syn syn;
public MyThread(Syn syn){
this.syn=syn;
}
@Override
public void run() {
syn.test();
}
}
package com.company;
public class Syn {
public void test(){
synchronized (this){//锁住类的实例对象,适用于只用一个对象产生多个线程
System.out.println("test开始...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test结束...");
}
}
}
public class Syn {
public synchronized void test(){
System.out.println("test开始...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test结束...");
}
}
public class Syn {
public void test(){
synchronized (Syn.class){//锁住Syn类
System.out.println("test开始...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test结束...");
}
}
}
volatile:
当一个共享变量被volatile修饰时,它会保证修改的值立即被更新到主存, 这里的”保证“ 是如何做到的?和 JIT的具体编译后的CPU指令相关吧?
volatile特性
内存可见性:通俗来说就是,线程A对一个volatile变量的修改,对于其它线程来说是可见的,即线程每次获取volatile变量的值都是最新的。
volatile的使用场景
通过关键字sychronize可以防止多个线程进入同一段代码,在某些特定场景中**,volatile相当于一个轻量级的sychronize**,因为不会引起线程的上下文切换,但是使用volatile必须满足两个条件:
1、对变量的写操作不依赖当前值,如多线程下执行a++,是无法通过volatile保证结果准确性的;
2、该变量没有包含在具有其它变量的不变式中,这句话有点拗口。
如何保证内存可见性?
在java虚拟机的内存模型中,有主内存和工作内存的概念,每个线程对应一个工作内存,并共享主内存的数据,下面看看操作普通变量和volatile变量有什么不同:
1、对于普通变量:读操作会优先读取工作内存的数据,如果工作内存中不存在,则从主内存中拷贝一份数据到工作内存中;写操作只会修改工作内存的副本数据,这种情况下,其它线程就无法读取变量的最新值。
2、对于volatile变量,读操作时JMM会把工作内存中对应的值设为无效,要求线程从主内存中读取数据;写操作时JMM会把工作内存中对应的数据刷新到主内存中,这种情况下,其它线程就可以读取变量的最新值。
volatile变量的内存可见性是基于内存屏障(Memory Barrier)实现的,什么是内存屏障?内存屏障,又称内存栅栏,是一个CPU指令。在程序运行时,为了提高执行性能,编译器和处理器会对指令进行重排序,JMM为了保证在不同的编译器和CPU上有相同的结果,通过插入特定类型的内存屏障来禁止特定类型的编译器重排序和处理器重排序,插入一条内存屏障会告诉编译器和CPU:不管什么指令都不能和这条Memory Barrier指令重排序。
wait()/notify机制:
厨师做菜是一个线程(notify),服务员传菜是一个线程(wait)。服务员要想传菜,必须让厨师把菜做出来并通知服务员菜做好了,在菜还没做出来之前,服务员只能等待,知道厨师通知菜做出来。
waitnotify类:
package com.company;
public class waitnotifytest implements Runnable {
volatile private Syn syn;
public waitnotifytest(Syn syn){
this.syn=syn;
}
@Override
public void run() {
synchronized (syn){
try {
System.out.println("开始wait time="+System.currentTimeMillis());
syn.wait();//让操作syn对象的线程等待,知道
System.out.println("结束wait time="+System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
waitnotify2类:
package com.company;
/**
* 用另外一个线程结束waitnotify线程的等待
*/
public class waitnotifltest2 implements Runnable {
Syn syn;
public waitnotifltest2(Syn syn){
this.syn=syn;
}
@Override
public void run() {
synchronized (syn){
System.out.println("开始notify time"+System.currentTimeMillis());
syn.notify();
System.out.println("结束notify time"+System.currentTimeMillis());
}
}
}```
Syn类:
package com.company;
public class Syn {
}
Main():
package com.company;
public class Main {
public static void main(String[] args) {
Syn syn=new Syn();
waitnotifytest waitnotifytest=new waitnotifytest(syn);
Thread thread1=new Thread(waitnotifytest);
//线程的任务是操作syn对象的值,所以要给syn对象上锁
thread1.start();
waitnotifltest2 waitnotifltest2=new waitnotifltest2(syn);
Thread thread2=new Thread(waitnotifltest2);
thread2.start();
}
}
运行结果: