系统进行资源分配和调用的独立单位
。每一个进程都有他自己的内存空间和系统资源。单进程的计算机只能做一件事情,而我们现在的计算机都可以做多件事情。举例:一边玩游戏(游戏进程),一边听音乐(音乐进程)。
也就是说现在的计算机都是支持多进程的,可以在一个时间段内执行多个任务。
线程是依赖于进程而存在。
(360是一个进程,同时运行360的不同功能是多线程);
一个进程中至少有一个线程。
(4)目的:开启多个线程是为了同时运行多部分代码。
为了提高应用程序的使用率
。程序的执行其实都是在抢CPU的资源,CPU的执行权。
多个进程是在抢这个资源,而其中的某一个进程如果执行路径比较多(线程多),就会有更高的几率抢到CPU的执行权。
我们是不敢保证哪一个线程能够在哪个时刻抢到,所以线程的执行有随机性。
注意并行和并发
: 垃圾回收线程(finalize())
也要先启动,否则很容易会出现内存溢出。 如果多线程中的某一条线程发生错误,会显示异常,并停止这条线程,但其他线程不受影响.
C/C++
去调用系统功能创建进程,然后由Java
去调用进程实现多线程1.多线程优点:
2.多线程缺点:
3.多线程有几种实现方案,分别是哪几种?
两种。
1.继承Thread类
2.实现Runnable接口
扩展一种:实现Callable接口。这个得和线程池结合。(一般可以不答)
假如我们的计算机只有一个CPU,那么CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。
线程有两种调度模型:
Java使用的是抢占式调度模型。
方法 | 定义 |
---|---|
public final int getPriority() |
获取线程对象的优先级 |
public final void setPriority(int newPriority) |
设置线程的优先级 |
public class ThreadPriorityDemo {
public static void main(String[] args) {
ThreadPriority tp1 = new ThreadPriority();
ThreadPriority tp2 = new ThreadPriority();
//设置线程名
tp1.setName("东方不败");
tp2.setName("岳不群");
// 获取默认优先级
System.out.println(tp1.getPriority());
//设置线程优先级
tp1.setPriority(10);
tp2.setPriority(1);
System.out.println(tp1.getPriority());
System.out.println(tp2.getPriority());
tp1.start();
tp2.start();
}
}
///////////////
5-----默认是5
10---设置后
1
5
东方不败:0
东方不败:1
注意:
线程优先级高仅仅表示线程获取的 CPU时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到比较好的效果。
sleep(long millis) |
线程休眠,单位是毫秒(ms)。(时间到了继续运行) |
---|---|
join() |
线程加入,只有这个线程完毕,其他线程才可以继续 |
yield() |
线程礼让,暂停当前线程,执行其他线程 |
setDaemon(boolean on) |
后台线程 :将该线程标记为守护线程或用户线程必须在启动线程前调用(一旦只剩下守护线程,后台线程立即结束) |
stop() |
中断线程 |
interrupt() |
中断线程,把线程的状态终止,并抛出一个异常,不会影响后续代码运行 |
toString() |
Thread.*currentThread*().toString(); Thread[Thread-0,5,main] |
public class SleepThread extends Thread{
public void run(){
for(int i = 0 ; i < 5 ;i++){
try{
Thread.sleep(2000);//此线程sleep时,会运行其他线程
//只能用try catch,不能throws,因为父类没抛这个异常,子类也不能抛
//Thread.sleep(1000);//可以直接写sleep(1000);而省略Thread
}catch(Exception ex){}
System.out.println(getName()+" "+i);
}
}
}
SleepThread s1 = new SleepThread();
SleepThread s2 = new SleepThread();
s1.setName("意");
s2.setName("而");
s1.start();
s2.start();
等待该线程终止,只有这个线程完毕,其他线程才可以继续
public class SleepThread extends Thread{
public void run(){
for(int i = 0 ; i < 50 ;i++){
System.out.println(getName()+" "+i);
}
}
}
//__________________
SleepThread s1 = new SleepThread();
SleepThread s2 = new SleepThread();
SleepThread s3 = new SleepThread();
s1.setName("111");s2.setName("222");s3.setName("333");
s1.start();
s1.join();//将1线程加入
s2.start();
s3.start();
注意:
理论上是一个线程执行一次后,等待下一个线程执行,然后第一个线程再执行第二次,……实际上可能有"误差"
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
Thread.yield();
}
}
//____________________________________________________
ThreadYield ty1 = new ThreadYield();
ThreadYield ty2 = new ThreadYield();
ty1.setName("林青霞");
ty2.setName("刘意");
ty1.start();
ty2.start();
方法 | 定义 |
---|---|
setDaemon(boolean on) |
将该线程标记为守护线程或用户线程。 当正在运行的线程只剩下守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用。 |
关羽张飞守护刘备,刘备完成走了,关羽张飞就也走了
ThreadDaemon td1 = new ThreadDaemon();
ThreadDaemon td2 = new ThreadDaemon();
td1.setName("关羽");
td2.setName("张飞");
// 设置守护线程(注意:必须在start()方法之前设置,否则会有异常!!)
td1.setDaemon(true);
td2.setDaemon(true);
td1.start();
td2.start();
Thread.currentThread().setName("刘备");//改一改main线程的名字
for (intx = 0; x < 5; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
}
但是不会立马结束,还会运行几次
方法 | 定义 |
---|---|
stop() : |
让线程停止,这一个线程停止 |
interrupt() |
停止程序,运行catch部分代码 |
public class ThreadStopDemo {
public static void main(String[] args) {
ThreadStop ts = new ThreadStop();
ts.start();
try {
Thread.sleep(3000);
ts.stop();//3s后会停止程序
} catch (InterruptedException e) {
e.printStackTrace();
}}}
--3s后程序停止
public static void main(String[] args) {
ThreadStop ts = new ThreadStop();
ts.start();
try {
Thread.sleep(3000);
ts.interrupt();//结束线程,运行catch内容
} catch (InterruptedException e) {
e.printStackTrace();
}
}
好处:不影响后续代码执行(继续运行catch之后的)
CPU同一时刻只能处理一个线程,多线程是多个线程轮流运行,允许运行但在等待的线程处于阻塞状态。
方法 | 定义 |
---|---|
sleep | 需要指定睡眠时间,单位是毫秒(ms)。(时间到了继续运行); |
wait() | 等待,自己无法醒来,用notify(),可以唤醒; |
1.继承Thread类
2.重写run方法
3.直接创建Thread的子类对象创建线程。
4.调用start方法开启线程并调用线程的任务run方法执行。
不是类中的所有代码都需要被线程执行的,为了区分哪些代码能够被线程执行,java提供了Thread类中的
run()
用来包含那些被线程执行的代码。(代码若想被多线程执行,必须写(封装)在run里面)一般来说,被线程执行的代码肯定是比较耗时的。
方法 | 定义 |
---|---|
run() |
定义类时需要覆写的方法,定义的是线程中要运行的代码块 |
start() |
开启线程,调用run方法 |
getName() |
获取线程的名称(Thread-编号(从0开始))run和start都是Thread-0 |
super(name) |
带参构造时,设置线程名称 |
Thread.currentThread() |
返回正在运行的线程对象;Thread thread = Thread.currentThread(); |
run()
:仅仅是封装被线程执行的代码,直接调用是普通方法,是按顺序运行 start()
:首先启动了线程,然后再由jvm去调用该线程的run()方法,是随机运行注意:
同一个线程只能调用一次!!(如my.start();只能用一次,第二次会显示异常IllegalThreadStateException)
//创建一个类,继承Thread,实现run方法
public class MyThread extends Thread {
public void run() {
for (int x = 0; x < 10000; x++) {
System.out.println(x);
}
}
}
//测试,调用线程直接new一个,start即可
public class MyThreadDemo {
public static void main(String[] args) {
// 创建两个线程对象
MyThread my1 = new MyThread();//开启一个新的线程
MyThread my2 = new MyThread();
my1.start();//执行线程
my2.start();
}
}
//-----
这是三个线程,my1,my2,main
名字 | 方法 | 获取的线程 |
---|---|---|
获取main线程名称 获取当前线程名称 |
Thread.currentThread().getName() | main |
当前的线程(main无法使用) | 方法1:Thread.currentThread().getName() 方法2:getName() |
Thread-0 |
public class MyThread extends Thread {
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
}
}}
public class MyThread extends Thread {
public MyThread() {}
////在这里可以设置线程名比如super("林");
public MyThread(String name){
super(name);
}
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
}
}
}
//在main方法中调用
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
System.out.println("main线程:"+Thread.currentThread().getName());//获取主线程名称
my1.start();
my2.start();
//*************************************************************************
main线程:main
Thread-1:0
Thread-1:1
方法 | 定义 |
---|---|
setName(String name) | 设置线程的名称(main线程无法改名) |
无参构造设置线程名
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
my1.setName("林青霞");
my2.setName("刘意");
my1.start();
my2.start();
////////
林青霞:0
刘意:0...
带参构造
MyThread my1 = new MyThread("林青霞");
MyThread my2 = new MyThread("刘意");
my1.start();
my2.start();
//****************
林青霞:13
刘意:0
1.为什么要给Thread类的构造函数传递Runnable的子类对象?
因为线程的任务都封装在Runnable接口子类对象的run方法中。所以要在线程对象创建时就必须明确要运行的任务。
2.为什么可以避免由于Java单继承带来的局限性
比如说,某个类已经有父类了,而这个类想实现多线程,但是这个时候它已经不能直接继承Thread类了(接口可以多实现implements,但是继承extends只能单继承),它的父类也不想继承Thread因为不需要实现多线程。
3.实现Runnable接口的好处:
1,实现Runnable接口。
2,重写run方法,将线程的任务代码封装到run方法中。
3,创建本类的对象
4,创建Thread类的对象,并把3步骤的对象作为构造参数传递。
class Demo implements Runnable{ //通过接口的形式完成。
public void run(){ //覆盖接口中的run方法
for(int x=0; x<100; x++)
System.out.println(Thread.currentThread().getName()+"....."+x);//获取名称,只能间接用
}
}
class ThreadDemo{
public static void main(String[] args) {
Demo d = new Demo();
//注意:MyRunnable对象只需要创建一个即可,多个Thread对象可以接收同一个MyRunnable对象
Thread t1 = new Thread(d);//通过Thread类创建线程对象,并传递Runnable。
Thread t2 = new Thread(d);
t1.start();
t2.start();
}
}
//-----结果---------
Thread-0...0
Thread-0...1
Demo my = new Demo();
Thread t1 = new Thread(my, "林青霞"); //创建,初始化线程名
Thread t2 = new Thread(my, "刘意");
t1.start();
t2.start();
//****************************
林青霞...84
刘意...64
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算。就会导致线程安全问题的产生。(例如,卖票,多线程,可能把数据卖到-1,因为各线程互相独立)
注:线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的。
就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程时不可以参与运算的。必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。(同一时间,只能有一个线程参与运算---同步机制)
StringBuffer sb = new StringBuffer();
Vector<String> v = new Vector<String>();
Hashtable<String, String> h = new Hashtable<String, String>();
(在定义时就锁了,因此效率低)
注意
:Vector是线程安全的时候才去考虑使用的,但是即使要安全,也不用
以前的:List
list1 = new ArrayList ();// 线程不安全
public static <T> List<T> synchronizedList(List<T> list)
List<String> list2 = Collections.synchronizedList(new ArrayList<String>()); // 线程安全
某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
public class SellTicket extends Thread {
// 定义100张票
// 为了让多个线程对象共享这100张票,我们其实应该用静态修饰
private static int tickets = 100;
public void run() {
// 定义100张票
// 每个线程进来都会走这里,这样的话,每个线程对象相当于买的是自己的那100张票,这不合理,所以应该定义到外面
// 是为了模拟一直有票
while (true) {
if (tickets > 0) {
System.out.println(getName() + "正在出售第" + (tickets--) + "张票");
}
}}}
调用
public class SellTicketDemo {
public static void main(String[] args) {
// 创建三个线程对象
SellTicket st1 = new SellTicket();
SellTicket st2 = new SellTicket();
SellTicket st3 = new SellTicket();
// 给线程对象起名字
st1.setName("窗口1"); st2.setName("窗口2"); st3.setName("窗口3");
// 启动线程
st1.start(); st2.start(); st3.start();
}
}
----
说明的是,通过继承Thread类来实现题中的需求并不是很好(tickets要用static修饰,并不太好),其实用Runnable接口更好地进行数据分离
public class SellTicket implements Runnable {
// 定义100张票
private int tickets = 100;
public void run() {
while (true) {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
}
}
}
调用
public class SellTicketDemo {
public static void main(String[] args) {
// 创建资源对象
SellTicket st = new SellTicket();
// 创建三个线程对象
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
// 启动线程
t1.start();t2.start(); t3.start();
}
}
这种代码会有安全隐患,比如一个窗口获取票后,过来一段时间才订,在这段时间其他窗口就运行了,此处以sleep模拟,让每个线程休息一段时间
if (tickets > 0) {
// 为了模拟更真实的场景,我们稍作休息
try {
Thread.sleep(100); // t1就稍作休息,t2就稍作休息
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(......);
}}}}
但是出问题了!!!
CPU的一次操作必须是原子性的(在读取tickets--的原来的数值和减1之后的中间挤进了两个线程而出现重复)
随机性和延迟导致的(三个线程同时挤进一个循环里,tickets--的减法操作有可能在同一个循环中被执行了多次而出现越界的情况,比如说tickets要大于0却越界到了-1)
也就是说,线程1执行的同时线程2也可能在执行,而不是线程1执行的时候线程2不能执行。
这就是线程的安全问题
同一时间,只能有一个线程访问该代码;
同步可以解决安全问题的根本原因就在那个对象synchronized上。该对象如同锁的功能。(对象锁,同步锁)
格式:
synchronized(对象){
需要被同步的代码 ;
}
Object obj = new Object();//同步,必须作为成员变量, 不能放在run内,
public void run(){
synchronized(obj){…}
}
public void run(){
synchronized(this或Ticket.class){…}
}
public synchronized void add(int num){...}
任意对象
票是按顺序减少的且没有了同票和负票
//卖票,共有100张票,多个线程同时卖票,一张票只能卖一次
public class SellTicket implements Runnable {
// 定义100张票
private int tickets = 100;
//创建锁对象
private Object obj = new Object();
public void run() {
while (true) {
synchronized (obj) {//必须用在run外面定义的obj,而不能用new Object,那样不是同一把锁,还会出错
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "张票");
}
}
}
}
}
调用
public class SellTicketDemo {
public static void main(String[] args) {
// 创建资源对象
SellTicket st = new SellTicket();
// 创建三个线程对象
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
// 启动线程
t1.start();
t2.start();
t3.start();
}
}
票是按顺序减少的且没有了同票和负票
public synchronized void add(int num){ … }
同步方法用的锁
:this
静态方法的锁对象是
:当前类名.class
,可以用对象.getClass()
方法获取也可以用 类名.class
表示;建议使用同步代码块。
public static synchronized void show(){}(其中num要该为static)
//定义出售的票源
public class Tickets implements Runnable{
private int ticket = 100;
public void run(){
while(true){
payTicket();
}
}
public synchronized void payTicket(){
if( ticket > 0){
try{
Thread.sleep(10);
}catch(Exception ex){}
System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--);
}
}
}
会出错,因为第一个synchronized的锁是d,而第二个同步方法的锁是this,会出错
- 方法:
- 方法1.把第一个的锁换成:
this
- 方法2.在第二个方法锁的第一行加上同样的锁
synchronized(d)
public class Tickets implements Runnable{
private int ticket = 100;
private int x = 0;
//run方法定义
public void run() {
while (true) {
if(x%2==0){
synchronized (d) {//this
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票 ");
}
}
}else {
sellTicket();
}
x++;
}
}
//-------------测试-----------
private static synchronized void sellTicket() {
if (tickets > 0) {
try {
// Thread.sleep(100);//延时
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票 ");
}
}
}
在总票数和同步方法上添加
static
出错,静态方法锁不能用this,需要用的是当前类的class对象:SellTicket.class
public class Tickets implements Runnable{
private static int tickets = 100;// 用静态方法锁,ticket必须加静态
private int x = 0;
public void run() {
while (true) {
if (x % 2 == 0) {
synchronized (this) {// SellTicket.class
if (tickets > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票 ");
}
}
} else {
sellTicket();
}
x++;
}
}
private static synchronized void sellTicket() {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票 ");
}
}
}
两个人到银行存钱,每人存3次,每次100‘
class Bank {// 建立银行类
private int sum;// 金库
public synchronized void add(int num) {// 同步函数(第二种写法),存钱
sum = sum + num;
try {
Thread.sleep(10);
} catch (InterruptedException e) {} // 延时等待,使顺序
System.out.println("sum=" + sum);// 显示金库余额
}
}
class Cus implements Runnable {// 定义线程类
private Bank b = new Bank();
public void run() {// 覆盖run方法
for (int x = 0; x < 3; x++) {// 存钱,存三次,每次100
b.add(100);
}
}
}// 循环有次数限制,不会出现死循环
class BankDemo {
public static void main(String[] args) {
Cus c = new Cus();// 实例化线程类
Thread t1 = new Thread(c);// Thread
Thread t2 = new Thread(c);
t1.start();// 开启线程
t2.start();
}
}
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
方法 | 定义 |
---|---|
void lock() | 获取锁 |
void unlock() | 释放锁 |
ReentrantLock
是Lock的实现类(序列化类)
public class SellTicket implements Runnable {//实现接口
private int tickets = 100;
// 定义锁对象
private Lock lock = new ReentrantLock();
public void run() {
while (true) {
try {
// 加锁
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
} finally {
// 释放锁
lock.unlock();// 放在finally中,使释放锁必须进行
}
}
}
}
1.定义锁对象类,两个锁A和B
public class MyLock {
public static final Object lockA=new Object();
public static final Object lockB=new Object();
}
2.线程任务类
public class ThreadTask implements Runnable {
int x = new Random().nextInt(2);// 0,1 获取[0,2)之间随机值
// 指定线程要执行的任务代码
public void run() {
while (true) {
if (x % 2 == 0) {
// 情况一
synchronized (MyLock.lockA) {
System.out.println("if-LockA");
synchronized (MyLock.lockB) {
System.out.println("if-LockB");
System.out.println("if大口吃肉");
} }
} else {
// 情况二
synchronized (MyLock.lockB) {
System.out.println("else-LockB");
synchronized (MyLock.lockA) {
System.out.println("else-LockA");
System.out.println("else大口吃肉");
} } }
x++;
} }}
3.测试类
public class ThreadDemo {
public staticvoid main(String[] args) {
// 创建线程任务类对象
ThreadTask task = new ThreadTask();
// 创建两个线程
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
// 启动线程
t1.start();
t2.start();
}
}
定义两个锁:
public class LockA {
// 做一个私有构造器,保证锁是私有的
private LockA() {}
// 为了让其他类可以调用,这样调用,用final使其他类不能修改
// 只能通过类名调用静态成员获得
public static final LockA locka = new LockA();
}
///////////////////
public class LockB {
private LockB() {}
public static final LockB lockb = new LockB();
}
线程任务类
public class DeadLock implements Runnable {// 写死循环
private int i = 0;
public void run() {
while (true) {
if (i % 2 == 0) {// 偶数
// 先进入A同步,再进入B同步
synchronized (LockA.locka) {
System.out.println("if...locka");// 先进入A同步
synchronized (LockB.lockb) {
System.out.println("if...lockb");
}
}
} else {
// 先进入B同步,再进入A同步
synchronized (LockB.lockb) {
System.out.println("else...lockb");// 先进入B同步
synchronized (LockA.locka) {
System.out.println("else...locka");
}
}
}
i++;// 一次奇数一次偶数
}
}
}
测试类
public class DeadLockDemo {
public static void main(String[] args) {
DeadLock dead = new DeadLock();
Thread t0 = new Thread(dead);
Thread t1 = new Thread(dead);
t0.start();
t1.start();
}
}
//------结果---
if...locka
if...lockb
else...lockb
if...locka 形成死锁
线程间通讯:多个线程在处理同一资源,但是任务却不同。(不同线程处理同一个资源)
例程:
//资源,一对一,输入一个,输出一个
class Resource {
String name;
String sex;
}
生产者
// 输入,生产者(输入),对资源对象Resource中成员变量赋值
class Input implements Runnable {//实现接口
Resource r;// 反参数传递,因为:输入输出是同一个资源,不能用new了,可以加private
Input(Resource r) { this.r = r; }// 反参数传递,构造函数,可以加public
public void run() {
int x = 0;
while (true) {
synchronized (r) {// 解决线程问题,与输出锁相同, 同步必须在while里面
if (x == 0) {// x两次切换赋值,使得好像输入很多
r.name = "mike";
r.sex = "nan";
} else {
r.name = "丽丽";
r.sex = "女女";
} }
x = (x + 1) % 2;// x的0、1变化
}}}
消费者
//输出,消费者(输出)
class Output implements Runnable {
Resource r;
Output(Resource r) { this.r = r;}
public void run() {
while (true) {
synchronized (r) {// 和输入要用相同的锁,同步必须在while里面
System.out.println(r.name + "....." + r.sex);
}
}
}
}
测试
class aaa {
public static void main(String[] args) {
Resource r = new Resource();// 创建资源。
Input in = new Input(r);// 创建任务
Output out = new Output(r);
Thread t1 = new Thread(in);// 创建线程,执行路径。
Thread t2 = new Thread(out);
t1.start();// 开启线程
t2.start();
}
}
虽然数据安全了,但是呢,一次一大片不好看(获取同一个数据一次而输出多次,也就是set一次却get了多次),我就想依次的一次一个输出(也就是set一次get一次)。
如何实现呢?
通过Java提供的等待唤醒机制解决。
在Object类中提供了方法:
方法 | 定义 |
---|---|
wait() |
让线程处于冻结状态,被wait的线程会被存储到线程池中。(释放CPU执行资格和执行权)this.wait(); |
notify() |
唤醒线程池中一个线程(任意)。notify(); |
notifyAll() |
唤醒线程池中的所有线程。notifyAll(); |
这些方法都必须定义在同步中。因为这些方法是用于操作线程状态的方法。必须要明确到底操作的是哪个锁上的线程。
因为这些方法的调用是依赖于锁对象的,而同步代码块的锁对象是任意锁。任意的对象调用的方式一定定义在Object类中。
wait | sleep |
---|---|
指定时间 | 都可以 |
暂停时 | 释放执行权,释放锁。(别的线程可以进去) |
方法
方法 | 定义 |
---|---|
if |
if(flag),只有一次,会导致不该运行的线程运行了。出现了数据错误的情况。(等待后,获得执行权不会再次进行判断flag)(一对一使用) |
while |
while(flag),解决了线程获取执行权后,是否要运行!(等待后,获得执行权可以再次进行判断flag)(多对多使用) |
notify |
只能唤醒一个线程,如果本方唤醒了本方法,没有意义。而且while判断标记+notify会导致死锁。(一对一使用) |
notifyAll |
解决了本方线程一定会唤醒对方线程的问题。(多对多使用) |
资源类
class Resource {
private String name;// 安全优化,私有化
private String sex;// 安全优化,私有化
private boolean flag = false;// 一对一输入输出
public synchronized void set(String name, String sex) {
if (flag) // 若资源有内容,暂时不输入,等输出后再赋值
try { this.wait();
} catch (InterruptedException e) {
}
// wait需要异常处理
this.name = name;
this.sex = sex;
flag = true;
this.notify();// 赋值完成,唤醒输出
}
public synchronized void out() {
if (!flag)// 若资源无内容,暂时不输出
try { this.wait();
} catch (InterruptedException e) {
}
System.out.println(name + "..." + sex);
flag = false;// 输出完成,定义flag无内容
this.notify();// 唤醒输入
}
}
输入
class Input implements Runnable {
Resource r;// 反参数传递,因为:输入输出是同一个资源,不能用new了
Input(Resource r) {
this.r = r;
}// 反参数传递,构造函数
public void run() {
int x = 0;
while (true) {
if (x == 0)// x两次切换赋值,使得好像输入很多
r.set("mike", "nan");
else
r.set("丽丽", "女女女女女女");
x = (x + 1) % 2;// x的0、1变化
} }}
输出
class Output implements Runnable {
Resource r;
Output(Resource r) {this.r = r;}
public void run() {
while (true) { r.out();
} }}
资源
public class Student {
String name;
int age;
boolean flag; // 判断默认情况是没有数据,如果是true,说明有数据
}
生产者
public class SetThread implements Runnable {
// 生产者
private Student s;
private int x = 0;
public SetThread(Student s) {
this.s = s;
}
public void run() {
while (true) {
synchronized (s) {
if (s.flag) { // 判断有没有
try {// 若有
s.wait(); // 等待(必须是锁的wait),并释放锁,释放CPU执行权;
} catch (InterruptedException e) {
e.printStackTrace();}
}
if (x % 2 == 0) {// 若没有,或没有了之后释放锁
s.name = "林青霞";
s.age = 27;
} else {
s.name = "刘意";
s.age = 30;
}
x++; // x=1
s.flag = true;// 生产完,修改标记,使进入消费
s.notify(); // 唤醒t2,抢CPU的执行权。
} // t1有,或者t2有
}
}
}
消费
public class GetThread implements Runnable {// 消费
private Student s;
public GetThread(Student s) {
this.s = s;
}
public void run() {
while (true) {
synchronized (s) {
if (!s.flag) {// 若此时没有
try {
s.wait(); // 等待。并立即释放锁。将来醒过来的时候,是从这里醒过来
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(s.name + "---" + s.age);// 当有内容的时候,
// 林青霞---27
// 刘意---30
s.flag = false; // 修改标记防止再次进入消费
s.notify();// 唤醒线程唤醒t1
}
}
}
}
测试
public class StudentDemo {
public static void main(String[] args) {
// 创建资源
Student s = new Student();
// 设置和获取的类
SetThread st = new SetThread(s);
GetThread gt = new GetThread(s);
// 线程类
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
// 启动线程
t1.start();
t2.start();
}
}
//-----结果----
林青霞---27
刘意---30
林青霞---27
刘意---30
林青霞---27
资源
class Resource {
private String name;// 商品名称
private int count = 1;// 烤鸭编号
private boolean flag = false;// 标记
public synchronized void set(String name) {// 生产设置烤鸭名字
while (flag)// while进行判断,如果正在烤
try {this.wait();}
catch (InterruptedException e) {} // 等待t1 t0
this.name = name + count;// 烤鸭+编号----若已经消费了
count++;// 下一个编号2 3 4
System.out.println(Thread.currentThread().getName() + "..生产者.." + this.name);// 生产烤鸭1
flag = true;
notifyAll();// 多线程,只唤醒一个可能出现死锁,需要唤醒所有线程
}
public synchronized void out() {// 消费t3
while (!flag)// 如果正在卖
try {this.wait();}
catch (InterruptedException e) {} // 等待t2 t3
System.out.println(Thread.currentThread().getName() + "...消费者........" + this.name);// 消费烤鸭1
flag = false;
notifyAll();
}
}
生产者
class Producer implements Runnable {// 生产者
private Resource r;
Producer(Resource r) {this.r = r;}// 构造
public void run() {
while (true) {r.set("烤鸭");} // 生产烤鸭
}
}
消费者
class Consumer implements Runnable {// 消费者
private Resource r;
Consumer(Resource r) {this.r = r;}
public void run() {
while (true) { r.out();} // 消费烤鸭
}
}
测试
class ProducerCustomerDemo {
public static void main(String[] args) {
Resource r = new Resource();// 实例化资源
Producer pro = new Producer(r);// 生产
Consumer con = new Consumer(r);// 消费
Thread t0 = new Thread(pro);// 线程t0负责生产
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(con);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
//----结果----
Thread-1..生产者..烤鸭136416
Thread-2...消费者........烤鸭136416
Thread-0..生产者..烤鸭136417
Thread-3...消费者........烤鸭136417
Thread-1..生产者..烤鸭136418
Thread-2...消费者........烤鸭136418
Thread-0..生产者..烤鸭136419
Thread-3...消费者........烤鸭136419
Thread-1..生产者..烤鸭136420
Thread-2...消费者........烤鸭136420
Thread-0..生产者..烤鸭136421
jdk1.5以后将同步和锁封装成了对象。并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显示动作。
Lock lock = new ReentrantLock();
Condition接口
:出现替代了Object中的wait、notify、notifyAll方法。将这些监视器方法单独进行了封装,变成Condition监视器对象。可以任意锁进行组合。可以有多个监视器
接口 | 方法 | 定义 |
---|---|---|
Lock接口 | Lock lock = new ReentrantLock(); | |
lock() | 获取锁。lock.lock(); | |
unlock() | 释放锁,通常需要定义finally代码块中。lock.unlock(); | |
Condition接口 | Condition con = lock.newCondition(); | |
await() | 代替wait()功能,con.await(); | |
signal() | 代替notify()功能,con.signal(); | |
signalAll() | 代替notifyAll()功能,con.signalAll(); |
资源
//jdk1.5以后将同步和锁封装成了对象。
import java.util.concurrent.locks.*;
class Resource {
private String name;
private int count = 1;
private boolean flag = false;
Lock lock = new ReentrantLock();// 创建一个锁对象。
// 通过已有的锁获取两组监视器,一组监视生产者,一组监视消费者。
Condition producer_con = lock.newCondition();// 监视生产锁
Condition customer_con = lock.newCondition();// 监视消费锁
public void set(String name) {// 去掉synchronized同步
lock.lock();// 获取锁---同步
try {// 异常处理
while (flag)// 用while,循环检测
try {
producer_con.await();
} catch (InterruptedException e) {
} // 生产等待
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName() + "...生产者5.0..." + this.name);// 生产烤鸭
flag = true;
customer_con.signal();// 生产完,释放另一组锁的一个,运行消费
} finally {
lock.unlock();
} // 释放锁
}
public void out() {
lock.lock();// 获取锁---同步
try {
while (!flag)// 用while,循环检测
try {
customer_con.await();
} catch (InterruptedException e) {
} // 消费等待
System.out.println(Thread.currentThread().getName() + "...消费者.5.0......." + this.name);// 消费烤鸭
flag = false;
producer_con.signal();// 消费后,释放另一组锁的一个,运行生产
} finally {
lock.unlock();
} // 释放锁
}
}
它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
我们也可以给线程设置分组
方法 | 定义 |
---|---|
public final ThreadGroup getThreadGroup() | 获取所属的线程组 |
public final String getName() | 获取名字 |
Thread(线程组名, 类名, 自定义线程) | 创建线程组 |
Void destroy() | 销毁此线程组及其所有子组 |
Void interrupt() | 中断此线程组中的所有线程 |
Void setDaemon() | 变为后台线程:守护线程:只有守护线程会结束程序 |
Void setMaxPriority() | 设置最高优先级 |
public class MyRunnable implements Runnable {
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}
测试
MyRunnable my = new MyRunnable();
Thread t1 = new Thread(my, "林青霞");
Thread t2 = new Thread(my, "刘意");
// 获取所在线程组的名字
// 线程类里面的方法:public final ThreadGroup getThreadGroup()
ThreadGroup tg1 = t1.getThreadGroup();
//获取线程组
ThreadGroup tg2 = t2.getThreadGroup();
// 线程组名的方法:public final String getName()
String name1 = tg1.getName();
//获取线程组名字
String name2 = tg2.getName();
System.out.println(name1);
System.out.println(name2);
//获取主线程线程组名字
System.out.println(Thread.currentThread().getThreadGroup().getName());
默认情况下,所有的线程都属于主线程组main
获取线程组名字,自己建立的线程属于main线程组
创建一个线程组
创建其他线程的时候,把其他线程的组指定为我们自己新建线程组
//创建一个新的线程组
ThreadGroup tg = new ThreadGroup("这是一个新的组");
MyRunnable my = new MyRunnable();
//Thread(ThreadGroup group, Runnable target, String name)
Thread t1 = new Thread(tg, my, "林青霞");
Thread t2 = new Thread(tg, my, "刘意");
System.out.println(t1.getThreadGroup().getName());
System.out.println(t2.getThreadGroup().getName());
//通过组名称设置后台线程,表示该组的线程都是后台线程---守护线程:若只剩下守护线程,程序结束
//设置守护线程
tg.setDaemon(true);
----创建成功
(1****)原因:创建和销毁线程成本是比较高的,而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。
(2)线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池
线程池主要用来**解决线程生命周期开销问题和资源不足问题**。通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使**应用程序响应更快**。另外,通过适当的调整线程中的线程数目可以**防止****出现资源不足**的情况。
(3)JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
方法 | 定义 |
---|---|
public static ExecutorService newCachedThreadPool() | 创建一个具有缓存功能的线程池 |
public static ExecutorService newFixedThreadPool(int nThreads) | 创建一个可重用的,具有固定线程数nThreads的线程池; ExecutorService pool = Executors.newFixedThreadPool(2); |
public static ExecutorService newSingleThreadExecutor() | 创建一个只有单线程的线程池,相当于上个方法的参数是1 |
Future submit(Runnable task) | pool.submit(new MyRunnable()); |
Future submit(Callable task) | 调用 |
Void shutdown() | 结束线程池 |
这些方法的返回值是ExecutorService
对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。
它提供了如下方法
import java.util.concurrent.ExecutorService;
public class MyRunnable implements Runnable {
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}
//_____________________________________________________________________
public class ExecutorsDemo {
public static void main(String[] args) {
// 创建一个线程池对象,控制要创建几个线程对象。
ExecutorService pool = Executors.newFixedThreadPool(2);
// 可以执行Runnable对象或者Callable对象代表的线程
pool.submit(new MyRunnable());//调用线程池中线程,提交一次调用一个线程,用完返回
pool.submit(new MyRunnable());
pool.shutdown();//结束线程池,没有他,程序一直不关闭
}
}
run方法没有返回值,不能抛出异常,而这个接口的方法call()可以。
这里指定的泛型其实是call()
方法的返回值类型。
Future<String> f = es.submit(new ThreadPoolCallable());
String s = f.get();//get是泛型
案例
public class MyCallable implements Callable {//可以直接设置call的类型 Callable<String>
public String call() throws Exception {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
}
return null;
}
}
测试
public class CallableDemo {
public static void main(String[] args) {
//创建线程池对象
ExecutorService pool = Executors.newFixedThreadPool(2);
//可以执行Runnable对象或者Callable对象代表的线程
pool.submit(new MyCallable());
pool.submit(new MyCallable());
//结束线程池
pool.shutdown();
}
}
使用多线程技术,求和 , 两个线程,1个线程计算1+100,另一个线程计算1+200的和
public class MyCallable implements Callable<Integer> {
private int number;
public MyCallable(int number) {
this.number = number;
}
public Integer call() throws Exception {//求1-number的和
int sum = 0;
for (int x = 1; x <= number; x++) {
sum += x;
}
return sum;
}
}
/////////////////////////
public class CallableDemo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 创建线程池对象
ExecutorService pool = Executors.newFixedThreadPool(2);
// 可以执行Runnable对象或者Callable对象代表的线程
Future<Integer> f1 = pool.submit(new MyCallable(100));
Future<Integer> f2 = pool.submit(new MyCallable(200));
// V get()
Integer i1 = f1.get();//获得结果---需要抛出异常
Integer i2 = f2.get();
System.out.println(i1);//显示结果
System.out.println(i2);
// 结束
pool.shutdown();
}
}
(1)匿名内部类的格式:
new 类名或者接口名() {
重写方法;
};
(2)本质:是该类或者接口的子类对象。
public class ThreadDemo {
public static void main(String[] args) {
// 1.继承Thread类来实现多线程-----只用一次
new Thread() {
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":"+ x);
}
}.start();
}
}
}
// 2.实现Runnable接口来实现多线程
new Thread(new Runnable() {
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}) {}.start();
}
加上这个,就是两个线程互相抢了
// 更有难度的
new Thread(new Runnable() {
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println("hello" + ":" + x);
}
}
}) {}.start();
hello也加入了抢,共三个
new Thread(new Runnable() {
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println("world" + ":" + x);
}
}
}.start();
全部运行的话,不会运行hello,只会运行world,还是3个
方法3:
Runnable r = new Runnable(){
public void run(){
System.out.println("###");
}
};
new Thread(r).start();
定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行。在Java中,可以通过Timer和TimerTask类来实现定义调度的功能
在开发中一般不会用Timer,因为太弱,开发中一般用框架:
Quartz是一个完全由java编写的开源调度框架。
Timer---在util包中---定时器类
方法 | 定义 |
---|---|
public Timer() | 创建一个新的计时器 |
public void schedule(TimerTask(任务),Date (时间)) | 安排在指定的时间执行的任务(1次) |
public void schedule(TimerTask(任务),long(延迟)) | 安排在指定延迟后执行的任务(1次) |
public void schedule(TimerTask (任务),long (时间),long period) | 安排指定的任务从指定的延迟后开始进行重复的固定延迟执行 |
public void schedule(TimerTask (任务),Date (时间),long (延迟)) | 安排指定的任务在指定的时间开始进行重复的固定延迟执行 |
TimerTask--在Object包中:由Timer安排为一次执行或重复执行的任务
方法 | 定义 |
---|---|
public abstract void run() | 此计时器要执行的操作 |
public boolean cancel() | 终止此计时器 |
Int purge() | 从此计时器的任务队列中移除所有已取消的任务 |
Long sheduleExecutionTime() | 返回此任务最近实际执行是已安排执行时间 |
// 做一个任务
class MyTask extends TimerTask {
private Timer t;//创建一个新的计时器
public MyTask(){}//构造
public MyTask(Timer t){ this.t = t;}//构造方法
public void run() {
System.out.println("beng,爆炸了");
t.cancel();//终止要在任务执行完结束
}
}
public class TimerDemo {
public static void main(String[] args) {
// 创建定时器对象
Timer t = new Timer();
//3s后运行,并结束任务
t.schedule(new MyTask(t), 3000);
}
}
循环爆炸:3秒后执行爆炸第一次,每隔2秒再继续炸---不用加结束了,不然会只运行一次
// 做一个任务
class MyTask2 extends TimerTask {
public void run() {
System.out.println("beng,爆炸了");
}
}
public class TimerDemo2 {
public static void main(String[] args) {
// 创建定时器对象
Timer t = new Timer();
// 3秒后执行爆炸任务第一次,每隔2秒再继续炸
t.schedule(new MyTask2(), 3000, 2000);
}
}
//需求:在指定的时间删除我们的指定目录(我使用项目路径下的demo)(只删除1次)
class DeleteFolder extends TimerTask {// 定义删除类
public void run() {
File srcFolder = new File("demo");
deleteFolder(srcFolder);// 调用删除方法
t.cancel();// 终止要在任务执行完结束
}
// 递归删除目录
public void deleteFolder(File srcFolder) {
File[] fileArray = srcFolder.listFiles();// 文件数组
if (fileArray != null) {// 若有内容
for (File file : fileArray) {// 遍历
if (file.isDirectory()) {// 若是文件夹
deleteFolder(file);// 遍历
} else {// 若是文件—删除,并输出名字
System.out.println(file.getName() + ":" + file.delete());
}
} // 若没有内容,删除文件夹
System.out.println(srcFolder.getName() + ":" + srcFolder.delete());
}
}
}
public class TimerTest {
public static void main(String[] args) throws ParseException {
Timer t = new Timer();
String s = "2014-11-27 15:45:00";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d = sdf.parse(s);// 定义删除时间
t.schedule(new DeleteFolder(), d);// 在指定时间删除指定内容
}
}
//多线程下的单例
//饿汉式--单例模式,简单,使用
class Single {// 公式
private static final Single s = new Single();
private Single() {}
public static Single getInstance() { return s;
}}
// 懒汉式--延迟加载单例模式,麻烦,但是考试多(技术含量高)
// 加入同步为了解决多线程安全问题。
// 加入双重判断是为了解决效率问题。
class Single {
private static Single s = null;
private Single() {}
public static Single getInstance() {// 同步使同一时间只进入一个线程
if (s == null) {// 加入双重判断是为了解决效率问题。
synchronized (Single.class) {// 加入同步为了解决多线程安全问题。
// 不可以用this.getClass(),因为是非静态的
if (s == null) s = new Single();
}}
return s;
}}
class SingleDemo {
public static void main(String[] args) {
Single s1 = Single.getInstance();// 单例调用
Single s2 = Single.getInstance();// 用s1,s2调用单例其他方法
}}
任务中都会有循环结构,只要控制住循环就可以结束任务。控制循环通常就用定义标记(条件:while之类)来完成。(若有wait(处于冻结状态),sleep(时间有时很长)无法结束)
interrupt
(中断)方法。可以使用interrupt()
方法将线程从冻结状态强制恢复到运行状态中来,让线程具备cpu的执行资格。 但是强制动作会发生了InterruptedException,记得要处理。
class StopThread implements Runnable {
private boolean flag = true;
public synchronized void run() {
while (flag) {
try {
System.out.println("线程");
wait();
} // 有wait,处于冻结状态,无法正常结束
catch (InterruptedException e) {// 中断异常处理
System.out.println(Thread.currentThread().getName() + ".." + e);
flag = false;// 中断后,结束
}
System.out.println(Thread.currentThread().getName() + "......++++");
}
}
public void setFlag() {
flag = false;
}
}
class StopThreadDemo {
public static void main(String[] args) {
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();// run中,直接进入wait状态,
t2.setDaemon(true); // t2设置为守护线程(后台线程)在启动线程前调用
t2.start();
int num = 1;
for (;;)// 无限循环
{
if (++num == 5) {// 若num==20,线程结束
System.out.println("interrupt前");
t1.interrupt();// t1中断,运行run中的中断异常处理
System.out.println("interrupt后");
break;
}
System.out.println("main...." + num);
}
System.out.println("over");// main结束
}
}
//-----结果---
线程//线程1输出,之后1进入wait
main....2//main输出
main....3
main....4
interrupt前//主函数,顺序
interrupt后//主函数,顺序(interrupt在线程中,并行)
over//主函数
线程//线程2输出,之后2进入wait
Thread-1.....java.lang.InterruptedException//interrupt1,运行run的catch
Thread-1......++++//线程1,顺序输出
1.如果错误 错误发生在哪一行?
class Test implements Runnable{
public void run(Thread t){}
}
----------------------------------------------------
错误在第一行,应该被abstract修饰:class abstract Test implements Runnable{
原因:这个类实现了接口,但是抽象方法run没有进行覆盖,那么类要定义为抽象类
2.输出的是什么?
class ThreadTest {
public static void main(String[] args) {
new Thread(new Runnable() {
public void run() {
System.out.println("runnable run");
}
}) {
public void run() {
System.out.println("subThread run");
}// run进行了覆写
}.start();
}}
-------------------------------------------------
输出:subThread run
若是后面{}里没有内容,输出:runnable run
正常写法:
new Thread(){
public void run(){
System.out.println("run覆写");
}
}.start();
原文:https://www.cnblogs.com/ziyue7575/p/12193933.html