概念
多进程概念
当前的操作系统都是多任务os
每个独立的任务称为一个进程
os将时间划分成多个时间片
每个时间片内将cpu分配给某一个任务,时间片结束,cpu将自动回收,再分配给另外任务。从外部看,所有任务是在同时执行。但是在cpu上,任务是按照串行依次运行(单核cpu)。如果是多核,多个进程任务可以并行。
多线程概念
一个子程序可以包括多个子任务,可串行/并行
每个子任务可以称为一个线程
如果一个子任务阻塞,程序可以将CPU调度另外一个子任务进行工作。这样CPU还是保留在本程序中,而不是被调度到别的程序(进程)中。这样,提高本程序所获得的CPU时间和利用率。
创建多线程
注意:
1、调用thread类的run方法来启动run方法,将会是串行运行(调用开始后,该线程未结束前不会执行其他)
2、调用start方法来启动run方法,将会是并行运行(多线程运行)
3、实现Runnable接口的对象必须包装在Thread类中才可以启动
不能直接对Runnable的对象进行start方法
eg Thread3 implements Runnable{,}
及Thread3是一个实现了Runnable接口的类
Thread3 tt = new Thread3(); // 创建一个Thread3的实例
Thread t = new Thread(tt);
t.start(); // runnable对象必须放在Thread类中才会运行
及java都是单根继承,如果用Thread继承则其无法再继承其他类。且Thread类的本身就是实现了Runnable的接口。
例子
public class ThreadDemo {
public static void main(String[] args) throws Exception {
TestThread1 tt = new TestThread1();
Thread t = new Thread(tt);
t.start();
while(true) {
System.out.println("MainThread is running");
Thread.sleep(1000);
}
}
}
class TestThread1 implements Runnable{
public void run() {
while(true) {
System.out.println("TestThread1 is running");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
多线程信息共享
通过共享变量达到信息共享
(1)static变量
(2)同一个Runnable类的成员变量
-------------Thread类的信息共享只能用static变量-------------------
结果 -卖出103张(有偏差)
public class ThreadDemo {
public static void main(String[] args) throws Exception {
new TestThread1().start();
new TestThread1().start();
new TestThread1().start();
new TestThread1().start();
}
}
class TestThread1 extends Thread{
// private int tickets = 100; 每个线程卖100张,没有共享
private static int tickets = 100; // 所有线程共享的变量
public void run() {
while(true) {
if(tickets > 0) {
System.out.println(Thread.currentThread().getName() + " is selling "+ tickets);
tickets--;
} else {
break;
}
}
}
}
注意下面的
TestThread1对象只被创建了一次,就是t。即内存中只有一个tickets
而new Thread(t)并没有创建TestThread1对象
而是把t包装成线程对象,然后启动
4个Thread使用的是同一个TestThread1对象
注意区分 是同一个线程start多次会报错
-------------实现Runnable的信息共享-------------------
结果 -卖出103张(有偏差)
public class ThreadDemo {
public static void main(String[] args) throws Exception {
TestThread1 t = new TestThread1();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
class TestThread1 implements Runnable{
private int tickets = 100;
public void run() {
while(true) {
if(tickets > 0) {
System.out.println(Thread.currentThread().getName() + " is selling "+ tickets);
tickets--;
} else {
break;
}
}
}
}
出错原因:
上述关键步骤:tickets–
在工作缓存中修改了一个变量值,但其他线程的工作缓存并没有更新
解决:采用volatile关键字修饰变量
保证一个变量在工作缓存中修改了,其他线程能马上看到
(保证不同线程对共享变量操作时的可见性)
解决工作副本的可见性问题
再看一个例子
public class ThreadDemo {
public static void main(String[] args) throws Exception {
TestThread1 t = new TestThread1();
t.start();
Thread.sleep(2000);
t.flag = false;
System.out.println("Main Thread is exiting");
}
}
class TestThread1 extends Thread{
boolean flag = true;
// volatile boolean flag = true; //volatile修饰的变量可以及时在各个线程里通知
public void run() {
int i = 0;
while(flag) {
i++;
}
System.out.println("Test Thread is exiting");
}
}
主线程输出了停止,但是子线程还在继续,说明子线程没看到false
即子线程用的是工作缓存中的flag,主存中的是false,工作缓存是true,而线程只依赖工作缓存
而volatile修饰的变量可以所有的线程都能立刻看到值的变化
关键步骤加锁限制
synchronized可以修饰一个代码块也可以修饰一个函数
synchronized关键字作为对象锁
正确:
public class ThreadDemo {
public static void main(String[] args) throws Exception {
TestThread1 t = new TestThread1();
new Thread(t, "Thread-0").start();
new Thread(t, "Thread-1").start();
new Thread(t, "Thread-2").start();
new Thread(t, "Thread-3").start();
}
}
class TestThread1 implements Runnable{
private volatile int tickets = 100; // 所有线程都可看到变化
//String str = new String("");
// 抢到str锁的可以进入
public void run() {
while(true) {
//synchronized (str) { // 同步代码块 一次只能有一个线程进入方法
sale();
//}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(tickets <= 0) {
break;
}
}
}
public synchronized void sale() {
// 同步函数
if(tickets > 0) {
System.out.println(Thread.currentThread().getName() + " is selling "+ tickets);
tickets--;
}
}
}
多线程生命周期管理
start后线程在running和runnable状态中切换,runnable表示准备就绪等待运行
线程阻塞&唤醒
-sleep 时间一到自我唤醒 sleep后进入block状态
-wait/notify/notifyAll 等待,需要别人来唤醒(notify唤醒)
join等待另一个线程结束
典例:生产者与消费者问题
1、wait() 与 notify/notifyAll 方法必须在同步代码块中使用
从改例子中可以看出,线程wait后需要其他线程notify,线程被动地暂停和终止,不能及时释放资源,很危险。
线程主动暂停和终止:
**利用定期监测共享变量的方式
*如果需要暂停和终止,先释放资源,再主动动作
public class InterruptTest {
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
TestThread1 t1 = new TestThread1();
TestThread2 t2 = new TestThread2();
t1.start();
t2.start();
// 让线程运行一会后中断
Thread.sleep(2000);
t1.interrupt(); // 被动中断
t2.flag = false; // 利用共享变量主动中断
System.out.println("main thread is exiting");
}
}
class TestThread1 extends Thread{
public void run(){
// 判断标志,当本线程被别人interrupt后,jvm会被本线程设置interrupted标记
while(!interrupted()) {
System.out.println("test thread1 is running");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
break;
}
}
System.out.println("test thread1 is exiting");
}
}
class TestThread2 extends Thread{
public volatile boolean flag = true;
public void run(){
while(flag) {
System.out.println("test thread2 is running");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("test thread2 is exiting");
}
}
多线程死锁(每个线程互相占有别人需要的锁)
典例:哲学家吃面问题
预防死锁:对资源进行等级排序
public class Deadlocks {
public static Integer r1 = 1;
public static Integer r2 = 2;
public static void main(String[] args) {
// TODO Auto-generated method stub
TestThread1 t1 = new TestThread1();
t1.start();
TestThread2 t2 = new TestThread2();
t2.start();
}
}
class TestThread1 extends Thread{
public void run() {
// 先要r1 再要r2
synchronized (Deadlocks.r1) {
// 加锁
try {
Thread.sleep(3);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (Deadlocks.r2) {
System.out.println("TestThread1 is running");
}
}
}
}
class TestThread2 extends Thread{
public void run() {
// 先要r1 再要r2
synchronized (Deadlocks.r2) {
// 加锁
try {
Thread.sleep(3);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (Deadlocks.r1) {
System.out.println("TestThread2 is running");
}
}
}
}
结果:无输出
修改排序:要求两个都是先拿1再拿2
······················································································
守护线程
普通线程的结束,是run方法运行结束
守护线程的结束,是run方法运行结束,或main函数运行结束
守护线程永远不要访问资源,如文件或数据库,因为是被动地暂停和终止(随main函数),来不及释放资源
// 守护线程
public class Daemonthread {
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
TestThread1 t = new TestThread1();
t.setDaemon(true); // 设置为守护线程
t.start();
Thread.sleep(1000);
System.out.println("Main thread is exiting");
}
}
class TestThread1 extends Thread{
public void run() {
while(true) {
System.out.println("TestThread1 is running");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
主线程结束后,test线程不在不再继续