一,宏观概念
1,进程和线程
- 进程是独立应用程序,线程是进程的一条执行路径。
- 一个进程通常有N个线程
2,多线程
指进程中的多个路径同时执行,主要目的是提高程序效率。
【举个栗子】:
打开网易云音乐,可以理解为一个进程,然后点开一首歌曲,这是一个线程,然后在播放歌曲的同时,可以在下边评论,这就是两个线程。
3,并发与并行
多线程是针对单核CPU的,也就是并发。
多核CPU的多个核心同时运算称为并行。
4,多线程的使用场景
多线程的本质是CPU时间片的快速切换,当并发操作次数很大时,可以忽略掉创建线程和线程切换的开销,但是如果并发量很小,多线程就显得多此一举了。
二,多线程创建方式
1.继承Thread,重写run方法
在晴朗早晨,和朋友一边散步一边聊天.....
CPU交替执行三件事,但切换速度很快,感觉上就是在同时进行......
class ThreadWalk extends Thread
{
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
System.out.println("Walk。。。。。");
}
}
}
class ThreadTalk extends Thread
{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("Talking........");
}
}
}
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println("Sunshine.....");
}
ThreadWalk threadWalk = new ThreadWalk();
threadWalk.start();
ThreadTalk threadTalk = new ThreadTalk();
threadTalk.start();
}
}
运行结果:
Sunshine.....
Sunshine.....
Sunshine.....
Sunshine.....
Sunshine.....
Sunshine.....
Sunshine.....
Sunshine.....
Sunshine.....
Sunshine.....
Walk。。。。。
Walk。。。。。
Walk。。。。。
Walk。。。。。
Walk。。。。。
Walk。。。。。
Walk。。。。。
Walk。。。。。
Walk。。。。。
Walk。。。。。
Talking........
Talking........
Talking........
Talking........
Talking........
Talking........
Talking........
Talking........
Talking........
Talking........
Process finished with exit code 0
2. 实现Runnable接口
可继承多个接口,弥补单继承的不足
class MyRunnable1 implements Runnable
{
int i=10;
public void run()
{
for(;i>0;)
{
System.out.printf("day"+"\n");
i--;
}
}
}
class MyRunnable2 implements Runnable
{
int j=10;
public void run()
{
for(;j>0;)
{
System.out.printf("night"+"\n");
j--;
}
}
}
public class Main
{
public static void main(String []args)
{
MyRunnable1 runnable1 = new MyRunnable1();
MyRunnable2 runnable2 = new MyRunnable2();
Thread thread1 = new Thread(runnable1);
Thread thread2 = new Thread(runnable2);
thread1.start();
thread2.start();
for(int k=0;k<10;k++)
{
System.out.printf("main thread..."+"\n");
}
}
}
运行结果:
main thread...
main thread...
main thread...
main thread...
main thread...
night
night
night
night
night
night
night
night
night
night
day
day
day
day
day
day
day
day
day
day
main thread...
main thread...
main thread...
main thread...
main thread...
3.匿名内部类实现多线程
public class Main
{
public static void main(String []args)
{
Thread thread = new Thread(
new Runnable() {
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
System.out.println("no Name thread。。。。");
}
}
}
);
thread.start();
for(int k=0;k<10;k++)
{
System.out.printf("main thread..."+"\n");
}
}
}
运行结果:
main thread...
main thread...
main thread...
main thread...
main thread...
main thread...
main thread...
no Name thread。。。。
no Name thread。。。。
no Name thread。。。。
no Name thread。。。。
main thread...
main thread...
main thread...
no Name thread。。。。
no Name thread。。。。
no Name thread。。。。
no Name thread。。。。
no Name thread。。。。
no Name thread。。。。
4,实现Callable接口
public class CreatThreadDemo4 implements Callable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CreatThreadDemo4 demo4 = new CreatThreadDemo4();
FutureTask<Integer> task = new FutureTask<Integer>(demo4); //FutureTask最终实现的是runnable接口
Thread thread = new Thread(task);
thread.start();
System.out.println("我可以在这里做点别的业务逻辑...因为FutureTask是提前完成任务");
//拿出线程执行的返回值
Integer result = task.get();
System.out.println("线程中运算的结果为:"+result);
}
//重写Callable接口的call方法
@Override
public Object call() throws Exception {
int result = 1;
System.out.println("业务逻辑计算中...");
Thread.sleep(3000);
return result;
}
}
5,使用线程池
public class CreatThreadDemo6 {
public static void main(String[] args) {
//创建一个具有10个线程的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
long threadpoolUseTime = System.currentTimeMillis();
for (int i = 0;i<10;i++){
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程执行了...");
}
});
}
long threadpoolUseTime1 = System.currentTimeMillis();
System.out.println("多线程用时"+(threadpoolUseTime1-threadpoolUseTime));
//销毁线程池
threadPool.shutdown();
threadpoolUseTime = System.currentTimeMillis();
}
}
6,使用定时器Timer
public class CreatThreadDemo5 {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("定时器线程执行了...");
}
},0,1000); //延迟0,周期1s
}
}
7,使用Java8新特性stream
public class CreatThreadDemo7 {
public static void main(String[] args) {
List<Integer> values = Arrays.asList(10,20,30,40);
//parallel 平行的,并行的
int result = values.parallelStream().mapToInt(p -> p*2).sum();
System.out.println(result);
//怎么证明它是并发处理呢
values.parallelStream().forEach(p-> System.out.println(p));
}
}
三,守护线程
主线程:main方法所在线程
用户线程(子线程):main方法中创建的子线程
守护线程:和main方法一起销毁的线程,比如说GC线程
非守护线程:main方法销毁依然执行的线程
thread.setDaemon(true);可设置线程为守护线程
class Main
{
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i <10 ; i++) {
System.out.println("子线程...");
}
}
});
// thread.setDaemon(true);
thread.start();
for (int i = 0; i <10 ; i++) {
System.out.println("main线程.....");
}
System.out.println("主线程销毁....");
}
}
四,join方法
在线程中调用另一个线程的join方法会暂时停止当前线程,将CPU资源给另一个线程先使用。
class Main
{
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
System.out.println("子线程...");
}
}
});
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i <10 ; i++) {
System.out.println("main线程.....");
}
System.out.println("主线程销毁....");
}
}
子线程...
子线程...
子线程...
子线程...
子线程...
子线程...
子线程...
子线程...
子线程...
子线程...
main线程.....
main线程.....
main线程.....
main线程.....
main线程.....
main线程.....
main线程.....
main线程.....
main线程.....
main线程.....
主线程销毁....
六,线程安全问题
当多个线程同时共享,同一个全局变量或静态变量,进行写的操作时,可能会发生数据冲突问题,也就是线程安全问题。但是进行读操作是不会发生数据冲突问题。
本质上是数据一致性问题。
Synchronized
- 关键字 synchronized可以保证在同一个时刻,只有一个线程可以执行某个方法或者某个代码块
- synchronized可保证一个线程的变化(主要是共享数据的变化)被其他线程所看到(保证可见性)
非静态同步方法
经典火车售票问题
class ThreadDemo implements Runnable
{
private static int count = 100;
//非静态同步方法
public synchronized void sale()
{
if(count>0)
{
count--;
System.out.println(Thread.currentThread().getName()+"剩余票数"+count);
}
}
@Override
public void run() {
while(count>0)
{
sale();
}
}
}
public class Main{
public static void main(String[] args) {
ThreadDemo threadDemo = new ThreadDemo();
Thread t1 = new Thread(threadDemo,"Window1");
Thread t2 = new Thread(threadDemo,"WIndow2");
t1.start();
t2.start();
}
}
静态同步方法
class ThreadDemo implements Runnable
{
private static int count = 100;
//静态同步方法
public synchronized static void sale()
{
if(count>0)
{
count--;
System.out.println(Thread.currentThread().getName()+"剩余票数"+count);
}
}
@Override
public void run() {
while(count>0)
{
sale();
}
}
}
public class Main{
public static void main(String[] args) {
ThreadDemo threadDemo = new ThreadDemo();
Thread t1 = new Thread(threadDemo,"Window1");
Thread t2 = new Thread(threadDemo,"WIndow2");
t1.start();
t2.start();
}
}
同步代码块
class ThreadDemo implements Runnable
{
private static int count = 100;
//同步代码块
public void sale()
{
synchronized(this)
{
if(count>0)
{
count--;
System.out.println(Thread.currentThread().getName()+"剩余票数"+count);
}
}
}
@Override
public void run() {
while(count>0)
{
sale();
}
}
}
public class Main{
public static void main(String[] args) {
ThreadDemo threadDemo = new ThreadDemo();
Thread t1 = new Thread(threadDemo,"Window1");
Thread t2 = new Thread(threadDemo,"WIndow2");
t1.start();
t2.start();
}
}
非静态同步方法和静态同步方法的区别
前者同步在对象层面上,后者同步在类的层面上
非静态同步方法和同步代码块的区别
前者在方法上,后者在方法内部。范围不同。
通常来说,范围越大,性能越差。
死锁
同步中嵌套同步,导致锁无法释放。
有两个线程并行执行,当线程1拿到了obj1锁,线程2拿到了obj2锁,就会出现互相等待的情况。
线程1拿着obj1等待obj2,线程2拿着obj2等着obj1.
class ThreadDemo implements Runnable {
private static int count = 100;
private Object obj1 = new Object();
private Object obj2 = new Object();
public void sale() {
synchronized (obj1) {
synchronized (obj2) {
if (count > 0) {
count--;
System.out.println(Thread.currentThread().getName() + "剩余票数" + count);
}
}
}
}
@Override
public void run() {
synchronized (obj2) {
synchronized (obj2) {
sale();
}
}
}
}
public class Main{
public static void main(String[] args) {
ThreadDemo threadDemo = new ThreadDemo();
Thread t1 = new Thread(threadDemo,"Window1");
Thread t2 = new Thread(threadDemo,"WIndow2");
t1.start();
t2.start();
}
}
多线程三大特性
-
原子性
一个操作或者多个操作要么全部执行,要么都不执行;在多线程中,原子性主要体现在数据一致性上。
-
可见性
当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
-
有序性
程序执行的顺序按照代码的先后顺序执行。
七,Java内存模型
Java内存模型(JMM)主要目标是定义程序中共享变量(线程共享)的访问规则。
JMM规定线程之间的共享变量存储在主内存中,每个线程都有一个本地内存(工作内存),本地内存存储了共享变量的副本。
Volatile关键字
volatile是一种轻量级的同步机制,可以保证可见性【及时将修改的变量刷新到主内存中】,但不能保证原子性,并且禁止重排序。
应用场景:全局共享变量
Volatile的可见性:一旦某个线程修改了该被volatile修饰的变量,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,可以立即获取修改之后的值。
Synchronized和Volatile的区别
JMM关于synchronized的两条规定:
- 线程加锁时,将清空工作内存中共享变量的值(本地内存已经有备份了)
- 线程解锁前,必须把本地内存的最新值刷新到主内存
(注意:加锁与解锁需要是同一把锁)
通过以上两点,可以看到synchronized能够实现可见性。同时,由于synchronized具有同步锁,所以它也具有原子性
Synchronized同时具有原子性和可见性,但效率低
Volatile仅具有可见性,但效率高
重排序
重排序是指CPU对代码的优化,但是不会对有依赖关系代码做重排序。
比如说
int x = 3; -----1
int y = 5; -----2
int z = x+y; -----3
1和2的执行顺序可能会发生改变,但一定在3之前。
【举个栗子】
定义两个静态变量,创建一个读线程,一个写线程。
如果是按照顺序执行的话,读线程会读到从1到5连续或者相同的数字。
public class Main {
private static int a=0;
private static int b=0;
public static void main(String[] args) {
Thread t1_write = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
while (true)
{
a = 1; // 这里可能发生重排序
b = 1; // 这里可能发生重排序
a = 2; // 这里可能发生重排序
b = 2; // 这里可能发生重排序
a = 3; // 这里可能发生重排序
b = 3; // 这里可能发生重排序
a = 4; // 这里可能发生重排序
b = 4; // 这里可能发生重排序
a = 5; // 这里可能发生重排序
b = 5; // 这里可能发生重排序
a = 4; // 这里可能发生重排序
b = 4; // 这里可能发生重排序
a = 3; // 这里可能发生重排序
b = 3; // 这里可能发生重排序
a = 2; // 这里可能发生重排序
b = 2; // 这里可能发生重排序
}
}
});
Thread t2_read = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
while(true)
{
System.out.println("a="+a);
System.out.println("b="+b);
System.out.println("=========================");
}
}
});
t1_write.start();
t2_read.start();
}
}
运行结果有的连续,有的并不连续,说明在执行读线程的时候,CPU进行了重排序。但是重排序只是一种情况。也有可能a=2时,执行读线程,输出b的时候a仍在执行,这种情况也会导致不连续。
如何用代码验证重排序目前还没有想出可行的方法,欢迎小伙伴们提出解决方法。^_^