程序,进程,线程
JVM中的程序计数器和虚拟机栈是线程私有的
方法区和堆是属于进程的,多个线程可以共享方法区和堆
线程的创建和使用
创建线程方式1:重写Thead run方法
- .新建类继承Thread类
- 新建的类重写run方法
- 创建新建的这个类对象调用start方法
start方法的作用:
- 启动当前线程
- 调用run方法
Thread.currentThread().getName() 获取当前线程的名称
package com.peppacatt.wswtest.javatest.basic;
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println("线程"+Thread.currentThread().getName()+":执行代码");
}
}
}
/**
* 多线程创建的几个步骤:
* 1.继承Thread类
* 2.重写run方法
* 3.调用start方法
*/
public class ThreadTest {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
for (int i = 0; i < 10000; i++) {
System.out.println("线程"+Thread.currentThread().getName()+":执行代码");
}
}
}
在执行到t.start()时还是在主线程内.当start方法被调用后,另一个线程就开始与主线程同时执行.start方法后的代码还是在主线程内
如果将t.start()换为t.run()那么run方法还是会执行,但是就没有另起一个线程了,始终就只有一个线程.程序执行的结果就是:先将run方法内的代码执行完再执行后面的方法
执行结果:
由于多个线程同时执行,cpu分给各个线程的时间片不同,可以看出,主线程和线程1是交替执行的
说明
匿名子类的方式创建线程
package com.peppacatt.wswtest.javatest.basic;
public class ThreadTest1 {
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println("线程" + Thread.currentThread().getName() + ":执行");
}
}
}.start();
new Thread(){
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println("线程" + Thread.currentThread().getName() + ":执行");
}
}
}.start();
for (int i = 0; i < 10000; i++) {
System.out.println("线程" + Thread.currentThread().getName() + ":执行");
}
}
}
执行结果:
创建线程方式2:重写Runnable run方法
- 新建类实现Runnable接口
- 新建的类重写run方法
- 创建Thread对象并将新建的类作为参数传入
- 调用对象的start方法
package com.peppacatt.wswtest.javatest.basic;
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("线程" + Thread.currentThread().getName() + ":执行" + i);
}
}
}
package com.peppacatt.wswtest.javatest.basic;
public class ThreadTest {
public static void main(String[] args) {
new Thread(new MyRunnable()).start();
for (int i = 0; i < 50; i++) {
System.out.println("线程" + Thread.currentThread().getName() + ":执行" + i);
}
}
}
Lambda方式简写
Runnable是一个函数式接口
package com.peppacatt.wswtest.javatest.basic;
public class ThreadTest {
public static void main(String[] args) {
new Thread(() -> {
for (int i = 0; i < 50; i++) {
System.out.println("线程" + Thread.currentThread().getName() + ":执行" + i);
}
}).start();
}
}
两种方式对比
创建线程方式3:实现Callable接口
代码示例:
package com.peppacatt.wswtest.javatest.basic;
import java.util.concurrent.Callable;
public class MyCallable implements Callable {
@Override
public Object call() throws Exception {
System.out.println("MyCallable");
return null;
}
}
package com.peppacatt.wswtest.javatest.basic;
import java.util.concurrent.FutureTask;
public class ThreadTestCallable {
public static void main(String[] args) {
MyCallable mc = new MyCallable();
FutureTask ft = new FutureTask(mc);
//FutureTask也实现了Runnable接口
Thread t = new Thread(ft);
t.start();
// try {
// //get()返回值就是MyCallable类中重写的call()的返回值
// ft.get();
// } catch (InterruptedException e) {
// e.printStackTrace();
// } catch (ExecutionException e) {
// e.printStackTrace();
// }
}
}
创建线程方式4:使用线程池
package com.peppacatt.wswtest.javatest.basic;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadTestThreadPool {
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(10);
es.execute(new MyRunnable());
}
}
Thread类中的常用方法
yield方法
yield方法就是释放当前CPU执行权,让CPU去执行其他线程,但是后面CPU在分配时间片的时候还是会分配到当前线程
package com.peppacatt.wswtest.javatest.basic;
public class ThreadTest2 {
public static void main(String[] args) {
new Thread() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println("线程" + Thread.currentThread().getName() + ":执行"+i);
if (i == 5000) {
System.out.println("------------------------释放当前cpu执行权---------------------------------");
Thread.yield();
}
}
}
}.start();
for (int i = 0; i < 10000; i++) {
System.out.println("线程" + Thread.currentThread().getName() + ":执行"+i);
}
}
}
可以看出当Thread-0执行到5000时就暂停,让其他线程去执行.下次一定时间片分配的时候,还是可能会分配到Thead-0
join方法
join方法就是在当前线程调用另一个线程的join方法的时候,当前线程进入阻塞状态,先把另一个线程的代码全部执行完毕再来执行当前线程
package com.peppacatt.wswtest.javatest.basic;
public class ThreadTest2 {
public static void main(String[] args) {
Thread t = new Thread() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("线程" + Thread.currentThread().getName() + ":执行" + i);
}
}
};
t.start();
for (int i = 0; i < 50; i++) {
System.out.println("线程" + Thread.currentThread().getName() + ":执行" + i);
if (i == 40) {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
执行结果:
可以看出mian线程在执行到40时,先让Thread-0把它的代码全部执行完毕,然后再来执行main线程
sleep
让当前线程睡眠指定的毫秒,线程进出阻塞状态,阻塞完之后再执行当前线程的后面代码.在当前线程阻塞的时候其他线程可以执行
package com.peppacatt.wswtest.javatest.basic;
public class ThreadTest3 {
public static void main(String[] args) {
new Thread() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("线程" + Thread.currentThread().getName() + ":执行" + i);
if (i == 40) {
try {
System.out.println("-------------------sleep---------------------------");
sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}.start();
for (int i = 0; i < 50; i++) {
System.out.println("线程" + Thread.currentThread().getName() + ":执行" + i);
}
}
}
可以看出Thead-0执行到40时,Thead-0线程沉睡了3秒,再执行后面的代码
在Thead-0线程沉睡的时候,其他线程可以执行
线程调度
线程的生命周期
卖票问题
要求:多个线程一起卖总共100张票
代码:
package com.peppacatt.wswtest.javatest.basic;
public class MyThread extends Thread {
private int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket > 0) {
System.out.println("线程" + Thread.currentThread().getName() + ":卖票票号" + ticket);
ticket--;
} else {
break;
}
}
}
}
package com.peppacatt.wswtest.javatest.basic;
public class ThreadTest4 {
public static void main(String[] args) {
MyThread t = new MyThread();
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t.start();
t1.start();
t2.start();
}
}
问题一:超卖
当前的ticket是每个线程同时拥有100张,加起来一共300张了
解决方式1:将ticket设为static
package com.peppacatt.wswtest.javatest.basic;
public class MyThread extends Thread {
private static int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket > 0) {
System.out.println("线程" + Thread.currentThread().getName() + ":卖票票号" + ticket);
ticket--;
} else {
break;
}
}
}
}
解决方式2: 用Runable
package com.peppacatt.wswtest.javatest.basic;
public class MyRunnable implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket > 0) {
System.out.println("线程" + Thread.currentThread().getName() + ":卖票票号" + ticket);
ticket--;
} else {
break;
}
}
}
}
package com.peppacatt.wswtest.javatest.basic;
public class ThreadTest4 {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread t = new Thread(mr);
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
t.start();
t1.start();
t2.start();
}
}
问题二: 错票和重票
错票
假设的当前只剩下最后一张票了,当前线程在阻塞处1发生了阻塞,后面代码还未执行.然后此时其他线程去执行ticket减1后ticket=0,该进程继续执行后面代码的再执行ticket-1=-1就产生了错票
package com.peppacatt.wswtest.javatest.basic;
public class MyRunnable implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket > 0) {
//阻塞处1
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() + ":卖票票号" + ticket);
ticket--;
//阻塞处2
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
} else {
break;
}
}
}
}
重票
如果当前票号为50,当前线程已经将票号为50的票卖了,然后在阻塞处2阻塞了,未执行-1.此时其他线程再买票的时候票号还是50.就产生了重票
package com.peppacatt.wswtest.javatest.basic;
public class MyRunnable implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket > 0) {
//阻塞处1
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println("线程" + Thread.currentThread().getName() + ":卖票票号" + ticket);
ticket--;
//阻塞处2
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
问题分析
重票和错票就是产生了线程安全问题,就是在当前线程的run方法还未执行完的时候,其他线程先执行了,再接着执行当前线程剩余的代码.
如果该run方法内存在共享数据,就会产生问题
在Java中,通过同步机制解决线程安全问题
线程同步
同步代码块
在同步代码块内,只会让当前线程先执行操作,如果过个线程同时执行到这里,需要等待当前线程执行完该代码块,其他线程才能执行
解决实现Runable方式
package com.peppacatt.wswtest.javatest.basic;
public class MyRunnable implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
synchronized (this) {
if (ticket > 0) {
//阻塞处1
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() + ":卖票票号" + ticket);
ticket--;
//阻塞处2
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
} else {
break;
}
}
}
}
}
解决继承Thread方式
new 一个共享的对象两种方式:
- obj
- 当前类.class
package com.peppacatt.wswtest.javatest.basic;
public class MyThread extends Thread {
private static int ticket = 100;
// private static Object obj = new Object();
@Override
public void run() {
while (true) {
// synchronized (obj) {
synchronized (MyThread.class) {
if (ticket > 0) {
//阻塞处1
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() + ":卖票票号" + ticket);
ticket--;
//阻塞处2
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
} else {
break;
}
}
}
}
}
同步方法
同步方法中的锁是this
解决实现Runable方式
package com.peppacatt.wswtest.javatest.basic;
public class MyRunnable implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
sale();
if (ticket <= 0) {
break;
}
}
}
public synchronized void sale() {
if (ticket > 0) {
//阻塞处1
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() + ":卖票票号" + ticket);
ticket--;
//阻塞处2
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}
}
}
解决继承Thread方式
同步方法需要加static
package com.peppacatt.wswtest.javatest.basic;
public class MyThread extends Thread {
private static int ticket = 100;
@Override
public void run() {
while (true) {
sale();
if (ticket <= 0) {
break;
}
}
}
public static synchronized void sale() {
if (ticket > 0) {
//阻塞处1
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() + ":卖票票号" + ticket);
ticket--;
//阻塞处2
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}
}
}
Lock锁
package com.peppacatt.wswtest.javatest.basic;
import java.util.concurrent.locks.ReentrantLock;
public class MyRunnable implements Runnable {
private int ticket = 100;
private ReentrantLock rl = new ReentrantLock(true);
@Override
public void run() {
while (true) {
try {
rl.lock();
if (ticket > 0) {
//阻塞处1
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() + ":卖票票号" + ticket);
ticket--;
} else {
break;
}
} finally {
rl.unlock();
}
}
}
}
ReetrantLock锁和synchronized锁的区别
同步的优缺点:
好处:
- 同步的方式解决了线程安全问题
坏处:
- 操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低
死锁
代码演示:
package com.peppacatt.wswtest.javatest.basic;
public class ThreadTest6 {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread() {
@Override
public void run() {
synchronized (s1) {
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2) {
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(() -> {
synchronized (s2) {
s1.append("c");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1) {
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}).start();
}
}
线程通信
wait() notify() notifyAll()的调用者必须是锁对象在下面的案例中相当于this.wait() 和 this.notify()
案例:
使用两个线程打印100-1,线程1和线程2交替打印
package com.peppacatt.wswtest.javatest.basic;
import java.util.concurrent.locks.ReentrantLock;
public class MyRunnable implements Runnable {
private int ticket = 100;
private ReentrantLock rl = new ReentrantLock(true);
@Override
public void run() {
while (true) {
synchronized (this) {
notify();
if (ticket > 0) {
//阻塞处1
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() + ":卖票票号" + ticket);
ticket--;
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
}
在此案例中:
当线程a第一次进入到synchronized代码块中,a执行notify方法没有可以唤醒的方法,操作完共享数据后,线程a执行wait方法进入阻塞状态并释放锁.
此时线程b进入了同步代码块中执行notify方法唤醒了线程a,然后b操作完共享数据之后也执行wait方法进入阻塞状态并释放锁
…