------<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培
训、.Net培训</a>、期待与您交流! -------
---------------Java之------多线程----------------
多线程状态图
1.进程:正在执行的程序。一个进程中至少有一个线程
每一个进程执行都有一个执行顺序(执行路径也叫控制单元)
2.线程:就是进程中的一个独立的控制单元。线程在控制着进程的执行。
多线程:就是一个进程中有多个线程(运行路径)
多线程允许多个线程并发执行,事实上,在某一个时刻,只能有一个线程在运行。cpu在做着快速的切换,以达到看上去是同时运行的效果。
3.Java JVM 启动时进程java.exe运行。该进程中的主线程启动(存在于main方法中)
4.创建进程的方法:
(1)第一种方法:继承Thread类
步骤:1.定义一个类继承Thread类
2.复写Thread类中的run方法(目的:将自定义代码存储在run方法中,让线程运行)
建立子类对象的同时线程也被创建。run方法中定义就是线程要运行的任务代码。
3.调用线程的start方法(该方法有两个作用:启动线程;调用run方法)
Thread类用于描述线程,该类就定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法,也就是说Thread类中的run方法,用于存储线程要运行的代码。因此,我们创建一个线程时,要覆写run方法
class A extends Thread{//继承Thread类
private String name;
A(String name){
super(name);//给线程取名
}
public void run(){//覆盖Thread类中的run方法。
for(int x=0;x<10;x++){
System.out.println(x+"--"+
Thread.currentThread().getName());//获取线程的名称
}
}
}
public class Demo {
public static void main(String[] args) {
A a1=new A("zhangsan");
A a2=new A("lisi");
a1.start();//开启线程
a2.start();
for(int x=0;x<20;x++){
System.out.println(x+"==="+Thread.currentThread().getName());
}
}
}
(2)第二种方法:声明实现Runnable接口(重点)
步骤:
1·定义类实现Runnable接口
class T implements Runnable
2·覆盖Runnable接口中的run方法将线程要运行的代码存放在该run方法中
3·通过Thread类建立线程对象
4·将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数
因为自定义的run方法所属的对象是Runnable接口的子类对象,
要让线程执行对象的run方法,就必须明确该run方法所属对象
5·调用Thread类的start方法开启线程,并调用Runnable接口子类的run方法
在定义线程时,建议使用实现方式,因为实现方式避免了单继承的局限性。
不同:
第一种方法存在Thread子类的run方法
而runnable存在接口子类的run方法中
/* 买票*/
class Ticket implements Runnable {
// 接口的子类发生异常,要try.处理不了,交给执行者处理
int num = 100;
//Object obj=new Object();//在Ticket类里面,相等于成员变量
public void run() {//run方法是覆写Runnable接口的方法,如果
//Object obj=new Object();放在里面,new了4次,等于有了4把锁
while (true) {
//synchronized(obj){
if (num > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName()
+ "--sale--" + num--);
//}
}
}
}
}
//结果多个窗口卖同一张票
public class hh {
public static void main(String[] args) {
Ticket t = new Ticket();// 创建线程任务对象
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
5.线程常用方法:
Thread currentThread():获取当前线程对象。
getName(): 获取线程名称。
设置线程名称:setName或者构造函数。
Test(String name){
super(name);
}
Sleep():控制线程休眠,单位为毫秒。
一个线程不能开启两次,会抛出无效线程状态异常
6.多线程的安全问题
多条语句执行同一个线程共享数据时,一个线程对多条语句
只执行一部分,还没有执行完,另一个线程参与进来执行导致
共享数据错误
判断是否存在安全隐患(重点)
查看覆写run方法的代码中是否存在共享数据。
run方法中调用的方法是否是赋值的add(int num)其中num就是共享数据。
并且对于共享数据的操作不止一条。这时就存在安全隐患。
解决办法:
对操作多条数据的语句,只能让一个线程先执行完,其他线yield()程在执行
在执行工程中,其他线程不可以参与执行。java对于多线程的安全问题,
指定了专业的解决方式-----------就是"同步代码块"。
7.同步代码块
// 这里对象就是使用的锁 Object obj=new Object();
synchronized(任意的对象obj)
{
需要被同步的代码; (注意 :只把需要同步的代码放进来,而不是把run方法中所有代码都同步)
}
8.同步函数
用synchronized 作为修饰符来修饰函数:叫做同步函数
同步函数用的锁是this。
建议使用同步代码块。
/**
*银行存钱:
*/
class Bank{
private int sum;
//Object obj=new Object();
public synchronized void add(int num){//同步函数的锁是this
//synchronized(obj){
sum=sum+num;
System.out.println("sum="+sum);
}
}
}
class Cus implements Runnable{
private Bank b=new Bank();
public void run(){
for(int i=0;i<3;i++){
b.add(100);
}
}
}
public class Test{
public static void main(String[] args){
Cus c=new Cus();
Thread t1=new Thread(c);
Thread t2=new Thread(c);
t1.start();
t2.start();
}
}
9.同步的前提(重点)
(1)对象如同锁,持有锁的线程可以在同步中执行。(锁就是一个对象)
没有持有锁的线程即使获得cpu的执行权,也进不去,因为没有获取锁
(2)同步的前提
·必须要有两个或者两个以上的线程
·必须是多个线程使用同一个锁
·必须保证同步中只能有一个线程在运行
(3)同步的好处和弊端:
·好处 :解决了多线程的安全问题
·弊端 :多个线程需要判断锁,较为消耗资源
(4)锁
Lock是一个接口,它其中有获取锁和解锁的方法 :替代了同步代码块或者同步函数。
将同步的隐式锁操作变成现实锁操作。 可以一个锁上加上多组监视器。
1.void lock() -- 获取锁
2.void unlock() -- 释放锁
3.newCondition() -- 返回绑定到此Lock实例的新Condition对象 (具有wait,notify的功能)
Condition
|-- await() 代替了 wait()
|-- signal() 代替了 notify()
|-- signalAll() 代替了 notifyAll()
将Synchronized替换成了Lock操作,
将Object类中的wait,notify,notifyAll替换成了Condition对象,该对象可以通过Lock锁进行获取。
实现了本方只唤醒对方的操作
import java.util.concurrent.locks.*;
class Resource {
private String name;
private int count = 1;
private boolean flag = false;
// 创建一个锁对象。
Lock lock = new ReentrantLock();
// 通过已有的锁获取该锁上的监视器对象。
// Condition con = lock.newCondition();
// 通过已有的锁获取两组监视器,一组监视生产者,一组监视消费者。
Condition producer_con = lock.newCondition();
Condition consumer_con = lock.newCondition();
public void set(String name)
{
lock.lock();
try {
while (flag)
try {
producer_con.await();
} catch (InterruptedException e) {
}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName()
+ "...生产者5.0..." + this.name);
flag = true;
// notifyAll();
// con.signalAll();
consumer_con.signal();
} finally {
lock.unlock();
}
}
public void out()
{
lock.lock();
try {
while (!flag)
try {
consumer_con.await();
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName()
+ "...消费者.5.0......." + this.name);
flag = false;
notifyAll();
// con.signalAll();
producer_con.signal();
} finally {
lock.unlock();
}
}
}
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 ProducerConsumerDemo2 {
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);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(con);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
10. 同步函数使用的是哪一个锁呢?
函数需要被对象调用,那么函数都有一个所属对象引用,就是this
所以同步函数使用的锁是this
11. 静态同步方法中使用的锁是:类名.class
因为静态方法中不可以定义this。静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。
即 :类名.class 或this.getclass, 该对象的类型是 :Class
12. 死锁 :同步中嵌套同步。两个线程持有的锁不同,而要相互访问,就有可能出现死锁情况.
class ThreadDemo implements Runnable {
//private boolean flag;
//ThreadDemo(boolean flag){
//this.flag=flag;
//}
private int num=1;
ThreadDemo(){
}
ThreadDemo(int num){
this.num=num;
}
public void run() {
//while(true)
//if (flag) {
if (num==1) {
synchronized (Lock.locka) {
System.out.println(Thread.currentThread().getName()
+ "if---locka");
synchronized (Lock.lockb) {
System.out.println(Thread.currentThread().getName()
+ "if---lockb");
}
}
} else {
//while(true)
synchronized (Lock.lockb) {
System.out.println(Thread.currentThread().getName()
+ "else---lockb");
synchronized (Lock.locka) {
System.out.println(Thread.currentThread().getName()
+ "else---locka");
}
}
}
}
}
class Lock {
public static final Object locka = new Object();
public static final Object lockb = new Object();
}
public class hh {
public static void main(String[] args) {
//ThreadDemo a = new ThreadDemo(true);
//ThreadDemo b = new ThreadDemo(false);
ThreadDemo a = new ThreadDemo();
ThreadDemo b = new ThreadDemo(2);
Thread t1 = new Thread(a);
Thread t2 = new Thread(b);
t1.start();
t2.start();
}
}
13.多线程间的通信
1. wait(); notify(); notifyall();
都是用在同步中。因为要对持有锁的线程操作,而只有同步中才有锁。
2.wait()和sleep()的区别 :
wait()释放资源,释放锁
sleep()释放资源,不释放锁
3.为什么这些操作线程的方法要定义在Object类中呢?
因为这些方法在操作同步线程时,都必须要标识它们所操作的线程持有的锁,
只有同一个锁上的被等待线程可以被同一个锁上的notify唤醒。
也就是说,wait和notify必须是同一个锁
而锁可以是任意对象,所以可以被任意对象调用的方法定义在Object类中
4. join() : 当A线程执行到了B线程的join()方法时,A就会等待B线程执行完毕,A才会执行
join()可以用来临时加入线程执行
5. 优先级 -- priority
setPrority -- 设置优先级
·MAX_PRIORITY 最高优先级
·MIN_PRIORITY 最低优先级
·NORM_PRIORITY 默认优先级
如:t1.setPriority(thread.MAX_PRIORITY); t1拥有最高的优先级执行
6. yield() : 暂停当前正在执行的线程对象,并释放执行权,然后再共同争夺执行权(执行其他线程)
14.线程的停止
1.stop方法已经过时,无法使用了。
2.停止线程只有一种方法 :run方法结束
因为线程运行代码一般都是循环,只要控制了循环即可。
控制循环通常就用定义标记来完成。
3.特殊情况 :
·当线程处于冻结状态时,就不会读取到标记,那么线程就不会结束。
·当没有指定的方式让冻结的线程恢复到运行状态时,就需要对冻结状态进行清除,
用interrupt方法强制将冻结状态的线程恢复到运行状态(即:获取执行资格)
4.interrupt方法(中断)
该方法是结束线程的冻结状态,使线程回到运行状态中来。
5.守护线程 -- setDaemon(boolean on)
on 如果标记为true,则将线程标记为守护线程(即:SetDamon(true))
当正在运行的线程都是守护线程时,Java虚拟机退出。
该方法必须在启动线程前调用
守护线程也是后台线程,而主线程等是前台线程,
后台线程开启后,和前台线程共同抢夺cpu的执行权运行,
当所有的前台线程都结束后,后台线程就自动结束
6.getThreadGroup 返回该线程所属的数组
7.MAX_PRIORITY----最大执行权
NORM_PRIORITY----默认执行权
MIN_PRIORITY----最小执行权
8.setPriority 设置优先级
9.isDaemon 设置为后台线程
15.单例模式的安全隐患
恶汉式单例模式
class Single{
private static Single s=new Single();
private Single(){};
public static Single getSingle(){
return s;
}
}
懒汉式单例模式
class Single1 {
private static Single1 s = null;
private Single1() {
};
public static/* synchronized */Single1 getSingle() {
// 函数加同步解决安全隐患。效率低。锁为类.class
if (s == null) {
//加一次判断,如果不为空,就不用判断同步锁,提高效率
synchronized (Single1.class) {
//同步代码块,解决安全隐患
if (s == null)
s = new Single1();
}
}
return s;
}
}
16.测试题
1.------------------------------------
public class ThreadTest {
public static void main(String[] args) {
new Thread(new Runnable() {
public void run() {
System.out.println("runnable run");
}
}) { // 子类覆写父类的run方法,运行子类。如果子类不存在,运行父类。
// 如果都没有,运行Thread。
public void run() {
System.out.println("subThread run");
}
}.start();
}
}
class Test implements Runnable {
public void run(Thread t) {
}
}
//如果错误 错误发生在哪一行?错误在第一行,应该被abstract修饰
------<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培
训、.Net培训</a>、期待与您交流! -------