黑马程序员_多线程总结

时间:2023-02-20 13:06:38

----------- android培训java培训、java学习型技术博客、期待与您交流! ------------

 
读解Thread类API
构造方法摘要 
Thread(Runnable target)           分配新的 Thread 对象。 
Thread(String name)           分配新的 Thread 对象。 
 方法摘要 
static Thread currentThread()           
说明:返回对当前正在执行的线程对象的引用。 
void start() 
说明:使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 
void stop() 
说明:已过时,该方法具有固有的不安全性。
void suspend() 
说明:已过时。 该方法已经遭到反对,因为它具有固有的死锁倾向。 
void resume()           
说明:该方法已过时,它只与 suspend() 一起使用,但 suspend() 已经遭到反对,因为它具有死锁倾向。 
static void sleep(long millis)
说明:在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。 
static void sleep(long millis, int nanos) 
说明:在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行)。 
static void yield() 
说明:线程让权,暂停当前正在执行的线程对象,并执行其他线程。 
void interrupt(): 说明:中断线程。 
void join():
说明:等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个线程运行结束,当前线程再由阻塞转为就绪状态。
void run() 说明:线程方法运行。 
String toString() 
说明:返回该线程的字符串表示形式,包括线程名称、优先级和线程组。 
-------------------------------------------------------------------------------------------------
线程的四种状态: 
就绪Runable——运行中Running——阻塞Blocked——死亡Dead(结束)
黑马程序员_多线程总结

----------------------------------------------------------------------------------------------
进程:正在进行中的程序(直译)。
进程其实对应一个应用程序在内存中的空间。
线程:就是进程中一个负责程序执行的执行路径
 
一个进程中可以有多个执行路径,称之为多线程。
一个进程中至少要有一个线程。

开启多个线程是为了同时运行多部分代码。
每一个线程都有自己运行的内容。这个内容可以称为线程要执行的任务。

多线程的好处与弊端:
好处:解决了多部分同时运行的问题。
弊端:线程太多 效率降低。
 
cpu的快速切换依赖于时间片。


JVM启动时就启动了多个线程,至少有两个线程可以分析的出来。
1.执行main函数的线程。
该线程的代码都定义在main函数中。
 
2.负责垃圾回收的线程。
finalize:当垃圾回收机制确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。
 
如何创建一个线程呢?
创建新执行线程有两种方法。
一种方法是将类声明为Thread的子类。该子类应该重写Thread类的run方法。
 
-----------------------------------------------------------------------------------------------
创建线程方式一:继承Thread类。

步骤:
1.定义一个类,继承Thread类。
2.覆盖Thread类中的run方法。
创建线程的目的是为了开启一条执行路径,去运行指定的代码,和其他代码实现同时运行 。
而运行的指定代码就是这个执行路径的任务。
JVM创建的主线程的任务都定义在了主函数中。
 
而自定义的线程的任务在哪儿呢?
Thread类用于描述线程,线程是需要任务的,所以Threaf也有对任务的描述。
这个任务就通过Thread类中的run方法来体现,也就是说,run方法就是封装
自定义线程运行任务的函数。

run方法中定义的就是线程要运行的任务代码。
开启线程是为了运行指定代码,所以只有继承Thread类,并覆写run方法。
将运行的代码定义在run方法中。

3.创建Thread类的子类对象,创建线程。
4.调用start方法开启线程。并调用线程的任务run方法执行。
 
Thread类中的方法线程名称。
可以通过Thread类中的getName获取线程的名称。Thread-编号(从0开始)
 
主线程的名字是main
 
currentThread返回当前正在执行的线程对象的引用。
Thread.currentThread().getName()返回当前正在运行的线程的名称。
 

class Demo extends Thread{  
private String name;
Demo(String name){
//super(name);Thread中的构造函数,可以给线程自定义名字。
this.name = name;
}
public void run(){
for (int i = 0; i < 10; i++) {
for (int j = -9999999; j < 99999999; j++) {}
System.out.println(name+"..."+i+".....name="+Thread.currentThread().getName());
}
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
Demo d1 = new Demo("旺财");
Demo d2 = new Demo("xiaoqiang");
d1.start();//开启线程,调用run方法
d2.start();
}
}


-----------------------------------------------------
准备扩展Demo类的功能,让其中的内容可以作为线程的任务执行。

通过接口的形式完成.
 创建线程的第二种方式:实现Runnable接口。
 
 步骤:
 1.定义类实现Runnable接口。
 
 2.覆盖接口中的run方法,将线程的任务代码封装到run方法中。
 
 3.通过Thread类创建线程对象,并将Runnable接口的子类对象作为构造函数的参数进行传递。
为什么呢?因为线程的任务都封装在Runnable接口子类对象的run方法中,
所以要在线程对象创建时就必须明确要运行的任务。 
 
 4.调用线程对象的start方法开启线程。
Runnable仅仅是将线程的任务进行了对象的封装。
 
实现Runnable接口的好处:
1.将线程的任务从线程的子类中分离出来,进行了单独的封装。
按照面向对象的思想将任务封装成对象。

2.避免了java单继承的局限性。


所以,创建线程的第二种方式较为常用。
class Ticket implements Runnable{
private int number=0;
boolean flag=true;
public void run(){
while(flag){
for (number = 200; number>=0; number--) {
System.out.println(Thread.currentThread().getName()+"is saleing "+number+"ticket!");
}
flag=false;
}
}
}


 
-------------------------------------------------------------------------------------------------------
线程安全问题产生的原因:
 
1.多个线程在操作共享的数据。
2.操作共享数据的线程代码有多条。
 
当一个线程在指向操作共享数据的多条代码过程中,其他线程参与了运算,
就会导致线程安全问题。


解决思路:
就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,
其他线程不可以参与运算。
必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。
 

在java中,用同步代码块就可以解决这个问题。
同步代码块的格式:

synchronized(对象){
需要被同步的代码;
}
 
同步的好处和弊端。
同步的好处:解决了线程的安全问题。
同步的弊端:相对降低了程序的效率,因为同步外的线程都会判断同步锁。
 
同步的前提:
同步中必须有多个线程,并使用同一个锁。
同步函数的锁是this.
 

同步函数和同步代码块的区别:
同步函数的锁是固定的this。
同步代码块的锁是任意的对象。
*********建议使用同步代码块***********。

静态的同步函数使用的锁是:该函数所属的字节码文件对象。
 
可以用getClass获取,也可以用 当前类名.class表示。
 
例如:Class clazz1 = t.getClass();
 
Class clazz2 = Ticket2.class;
 
-----------------------------------------------------------------------------------------


/**
* 饿汉式
* @author 张熙韬
*
*/
/*class Single{
private Single(){};
private static final Single instance=new Single();
public static Single getInstance(){
return instance;
}
}*/


/**
* 懒汉式
*/
class Single{
private Single(){};
private static Single s=null;

/*//同步方法比较低效率
public static synchronized Single getInstance(){
if(s==null){
s=new Single();
}
return s;
}*/

/**高效的同步方法的饿汉式
1.静态的同步函数使用的锁是:该函数所属的字节码文件对象。
2.懒汉式可以延迟加载
3.多线程访问会出现安全问题,需采用同步代码块!
*/
public static Single getInstance(){
if(s==null){
synchronized (Single.class) {
if(s==null){
s=new Single();
}
}
}
return s;
}

}


 
--------------------------------------------------------------------------
 
死锁:常见情景之一:同步的嵌套。同步中还有同步。(面试中可能会出现)

/**
* 死锁的一个例子!
* @author 张熙韬
*
*/
class Test111 implements Runnable{
private boolean flag;
public Test111(boolean flag){
this.flag=flag;
}

public void run(){
if(flag){
synchronized (MyLock.myLock_A) {
System.out.println("if....myLock_A");
synchronized (MyLock.myLock_B) {
System.out.println("if....myLock_B");
}
}
}else {
synchronized (MyLock.myLock_B) {
System.out.println("else....myLock_B");
synchronized (MyLock.myLock_A) {
System.out.println("else....myLock_A");
}
}
}
}
}


class MyLock{
static Object myLock_B=new Object();
static Object myLock_A=new Object();
}


public class DeadLockTest {

public static void main(String[] args){
Thread t1=new Thread(new Test111(true));
Thread t2=new Thread(new Test111(false));
t1.start();
t2.start();
}
}


----------------------------------------------------------------------------------
 
线程间通讯:
多个线程在处理同一资源,但是任务却不同。


等待唤醒机制:

涉及的方法:
1.wait();让线程处于冻结状态,被wait的线程会被存储到线程池中。
2.notify();唤醒线程池中的任意一条线程。
3.notifyAll();唤醒线程池中所有的线程。


这些方法都必须定义在同步中。
因为这些方法是用于操作线程状态的方法,
必须要明确到底操作的是哪个锁上的线程。
 
r.wait();  r.notify();
 
 问设么操作线程的方法,wait notify  notifyAll定义在了Object中。
 因为这些方法是监视器的方法。监视器其实就是锁。
 锁可以是任意的对象,而任意的对象调用的方法一定定义在Object中。


多生产者多消费者问题。 

if判断标记只有一次,会导致不该运行的线程运行了,会出现数据错误的情况。
while判断标记,解决了线程获取执行权后,是否要运行。
 
notify:只能唤醒一个线程,如果唤醒了本方,没有意义。而且while判断标记+notify会导致死锁。
notifyAll 解决了本方线程一定会唤醒对方线程的问题。

public class ProducerConsumerDemo {


/**
* 对于多个生产者和消费者。 为什么要定义while判断标记。 原因:让被唤醒的线程再一次判断标记。
*
* 为什么定义notifyAll, 因为需要唤醒对方线程。 因为只用notify,容易出现只唤醒本方线程的情况。 导致程序中的所有线程都等待。
*/


public static void main(String[] args) {
Resource r = new Resource();
new Thread(new Producer(r)).start();
new Thread(new Producer(r)).start();
new Thread(new Consumer(r)).start();
new Thread(new Consumer(r)).start();
}
}


class Resource {
private String name;
private int count = 1;
private boolean flag = false;


public synchronized void set(String name) {


while (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}


System.out.println(Thread.currentThread().getName() + "生产者" + this.name);
this.name = name + "--" + count++;
flag = true;


this.notifyAll();
}


public synchronized void Out() {
while (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+ "--------消费者--------" + this.name);
flag = false;


this.notifyAll();
}


}


class Producer implements Runnable {
Resource resource;


public Producer(Resource resource) {
this.resource = resource;
}


public void run() {
while (true) {
resource.set("+商品+");
}
}
}


class Consumer implements Runnable {
Resource resource;


public Consumer(Resource resource) {
this.resource = resource;
}


public void run() {
while (true) {
resource.Out();
}
}
}


----------------------------------------------------------------- 
JDK1.5解决办法:
jdk1.5以后将同步和锁封装成了对象。
 
并将操作锁的隐式方式定义到了该对象中,将隐式的动作变成了显示动作。

Lock接口:出现替代了同步代码块或者同步函数。将同步的隐式锁操作变成了显示锁操作。
 
同时更为灵活,可以一个锁上加上多组监视器。
 
lock():获取锁
 
unlock():释放锁,通常需要定义在finally代码块中。
Condition接口:出现替代了object中的wait  notify  notifyAll方法。
 
将这些监视器方法单独进行了封装,变成Condition监视器对象。
 
可以和任意的锁进行组合。
 
await();
 
signal();
 
signalAll();
 
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


/**
* JDK1.5 中提供了多线程升级解决方案。 将同步Synchronized替换成现实Lock操作。
* 将Object中的wait,notifynotifyAll,替换了Condition对象。 该对象可以Lock锁 进行获取。
* 该示例中,实现了本方只唤醒对方操作。
*
* Lock:替代了Synchronized lock unlock newCondition()
*
* Condition:替代了Object wait notify notifyAll await(); signal(); signalAll();
*/


public class ProducerConsumerDemo2 {


/**
* 对于多个生产者和消费者。 为什么要定义while判断标记。 原因:让被唤醒的线程再一次判断标记。
*
* 为什么定义notifyAll, 因为需要唤醒对方线程。 因为只用notify,容易出现只唤醒本方线程的情况。 导致程序中的所有线程都等待。
*/


public static void main(String[] args) {
Resource2 r = new Resource2();
new Thread(new Producer2(r)).start();
new Thread(new Producer2(r)).start();
new Thread(new Consumer2(r)).start();
new Thread(new Consumer2(r)).start();
}
}


class Resource2 {
private String name;
private int count = 1;
private boolean flag = false;


private Lock lock = new ReentrantLock();

Condition conn_pro = lock.newCondition();
Condition conn_cons = lock.newCondition();


public void set(String name) throws InterruptedException {
lock.lock();
try {
while (flag) {
try {
conn_pro.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}


System.out.println(Thread.currentThread().getName() + "生产者"
+ this.name);
this.name = name + "--" + count++;
flag = true;


conn_cons.signal();
} finally {
lock.unlock();
}


}


public synchronized void Out() throws InterruptedException {
lock.lock();
try {
while (!flag) {
try {
conn_cons.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()
+ "--------消费者--------" + this.name);
flag = false;


conn_pro.signal();
} finally {
lock.unlock();
}
}


}


class Producer2 implements Runnable {
Resource2 resource;


public Producer2(Resource2 resource) {
this.resource = resource;
}


public void run() {
while (true) {
try {
resource.set("+商品+");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}


class Consumer2 implements Runnable {
Resource2 resource;


public Consumer2(Resource2 resource) {
this.resource = resource;
}


public void run() {
while (true) {
try {
resource.Out();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}


-------------------------------------------------------------------------------
多线程中的一点小细节:
wait和sleep的区别。
 
1.wait可以指定之时间也可以不指定。
  sleep必须指定之间。(sleep一定会醒,wait不一定会醒)
 
2.在同步中时,对于cpu的执行权和锁的处理不同。
  wait:释放执行权,释放锁。
  sleep:释放执行权,不释放锁。


同步中可以有多条线程,但是只有一个线程在执行,谁拿锁谁执行。

停止线程:
1.stop方法(已过时)。
2.run方法结束。
 

怎么控制线程的任务结束呢?
任务中都会有循环结构,只要控制住循环就可以结束任务。

控制循环通常就用定义标记来完成。
 
但是如果线程处于冻结状态,无法读取标记,如何结束呢?
可以使用interrupt();方法将线程从冻结状态强制恢复到运行状态中来,
让线程具备cpu的执行资格。
但是强制动作会发生InterruptedException异常,记得要处理。

setDaemon()当正在运行的线程都是守护线程时,虚拟机退出。
如果所有的前台线程都结束,后台线程无论处于何种状态都会自动结束。
toString()线程的字符串表现形式。
 
线程优先级在1到10之间10个数。
优先级越大被执行到的几率越大。
t1.setPriority(Thread.MAX_PRIORITY);设置线程t1的优先级为10.
 
默认优先级是5
线程组:ThreadGroup
yield()方法  可以使线程暂停,暂时释放执行权。
 
-----------------------------------------------------------------------------------
/**
* join: 当A线程执行到了B线程的.join()方法时,A就会等待。
* 等B线程都执行完,A才会执行。 join可以用来临时加入线程执行。
*/


class Demo implements Runnable {
public void run() {
for (int x = 0; x < 70; x++) {
System.out.println(Thread.currentThread().getName().toString() + "..." + x);
//使用频率不高,面试
Thread.yield();
}
}
}


public class JoinDemo {
public static void main(String[] args) throws Exception {
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.setPriority(Thread.MAX_PRIORITY);
t1.start();
// t1.join();
t2.start();
//t1.join();
for (int x = 0; x < 80; x++) {
System.out.println("main========" + x);
}
System.out.println("结束啦!");
}
}


-----------------------------------------------------------------------------------------

JAVA多线程实现的两种模型
区别与联系:
Runnable是Thread的接口,在大多数情况下“推荐用接口的方式”生成线程,
因为接口可以实现多继承,况且Runnable只有一个run方法,很适合继承。
在使用Thread的时候只需要new一个实例出来,调用start()方法即可以启动一个线程。 
Thread Test = new Thread(); 
Test.start(); 
在使用Runnable的时候需要先new一个实现Runnable的类的实例,
之后,创建线程时将线程与该实例关联(其本质是让该线程执行时调用该实例中的run方法)。 

------------------------------------------------------------------------------------------------------------------------------------------

----------- android培训java培训、java学习型技术博客、期待与您交流! ------------