线程_多线程_同步机制

时间:2022-09-03 17:32:50

一,线程介绍:
1:概念:线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。线程(thread, *称 执行绪)是"进程"中某个单一顺序的控制流。也被称为轻量进程(lightweight processes)。计算机科学术语,指运行中的程序的调度单位。我们把正在计算机中执行的程序叫做"进程"(Process),而不将其称为程序(Program)。所谓"线程"(Thread),是"进程"中某个单一顺序的控制流。 2,线程:进程中的一个执行单元,负责进程中的程序的运行,一个进程中至少要有一个线程,一个进程是可以有多个线程的,这个应用程序也可以称之为多线程程序。 3,多线程:线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。多线程的使用可以合理使用cpu的资源,如果线程过多会导致降低性能。
二,Thread类:
1,介绍:线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。

每个线程都有一个优先级,高优先级线程的执行优先于低优先级线程。每个线程都可以或不可以标记为一个守护程序。当某个线程中运行的代码创建一个新Thread 对象时,该新线程的初始优先级被设定为创建线程的优先级,并且当且仅当创建线程是守护线程时,新线程才是守护程序。 

2,方法介绍: 1,获取线程名称:
thread:currentThread()获取当前线程对象,获取名称通过getName();方法。
主线程名称:main,自定义线程:thread-1,线程为多个时后面数字顺延。
2,join()   等待该线程终止。
                join(long millis)  等待该线程终止的时间最长为millis 毫秒。
 
join
(long millis, int nanos)
 等待该线程终止的时间最长为millis 毫秒 +nanos 纳秒。
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);

t1.start();
t1.setPriority(Thread.MAX_PRIORITY);

t2.start();
//t1.join();//特点:当a线程执行到了b线程的join方法时 a就会等待 等b线程都执行完,a才会执行
//join用来临时加入线程执行。
3,run()  如果该线程是使用独立的Runnable 运行对象构造的,则调用该Runnable 对象的run 方法;否则,该方法不执行任何操作并返回。 4,sleep(long millis)  在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。      sleep(long millis, int nanos) 在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。 5,start() 使该线程开始执行;Java 虚拟机调用该线程的run 方法。 6,yield() 暂停当前正在执行的线程对象,并执行其他线程。
三,创建线程: 1,创建线程有两种方式:
1,继承thread类
1.1定义一个类继承thread。
1.2重写run方法。
1.3创建子类对象,就是创建线程对象。
1.4调用start方法,开启线程并让线程执行,同时还会告诉jvm去掉用run方法。
2,实现runnable接口。
2.1定义类实现Runnable接口:避免了继承Thread类的单继承局限性,
2.2覆盖接口中的run方法。将线程任务代码定义到run方法中。
2.3创建thread类的对象:只有创建Thread类的对象才可以创建线程。
2.4将Runnable接口的子类对象作为参数传递给Thread类的构造函数。因为线程已经被封装到Runnable接口的run方法中。而这个run方法所属于Runnable接口的子类对象,所以将这个子类对象作为参数传递给Thread的构造函数,这样,线程对象创建时就可以明确要运行的线程的任务。
2.5调用Thread类的start方法开启线程。
代码实例:
package thread;

public class ThreadDemo {

public static void main(String[] args) {
//第一种方法实现:
thread1 t1 = new thread1("one");
thread1 t2 = new thread1("two");
t1.start();
t2.start();

////第二种实现方法:
//thread2 t1 = new thread2("one");
//thread2 t2 = new thread2("two");
//Thread tt1 = new Thread(t1);
//Thread tt2 = new Thread(t2);
//tt1.start();
//tt2.start();
for(int z = 0;z<=10;z++){
System.out.println("--main---"+z);
}
}
}
//方法一 继承thread类:
class thread1 extends Thread{
private String name;
thread1(String name){
this.name=name;
}
public void run(){
for(int x=0;x<10;x++){
System.out.println(Thread.currentThread()+"="+x);
}
}
}
//方法二:实现runnable接口:
class thread2 implements Runnable{

private String name;
thread2(String name){
this.name=name;
}
public void run(){
for(int x=0;x<10;x++){
System.out.println("thread--"+name+Thread.currentThread()+"="+x);
}
}
}

2,继承Thread类和实现Runnable接口两种方式比较:
1,第二种方式实现Runnable接口避免了单继承的局限性。所以较常用。实现Runnable接口的方式,更加符合面向对象,线程分为两部分,一部分线程对象,一部分线程任务。 2,第一种继承Thread类,线程对象和线程任务耦合在一起,一旦创建Thread类的子类对象,即是线程对象,又有线程任务。实现Runnable接口是将线程任务单独分离出来封装成对象,类型就是Runnable接口类型。Runnable接口对线程对象和线程任务进行解耦。
四,线程间安全问题: 1,问题产生的原因:
1,多个线程在操作共享的数据。
2,线程任务操作共享数据的代码有多条(运算有多个)。
2,解决思路: java中解决此问题时,是通过代码块来完成的。
同步代码块:synchronized(对象){需要被同步的代码}
同步代码块解决了多线程安全问题。
3,同步前提:同步前提:必须保证多个线程在同步中使用同一个锁。锁:使用的是this:
4,同步的体现形式:
1,同步代码块:
synchronized (对象obj){
//代码
}

2,同步函数:
public synchroizd void function(){//同步函数
//代码
}
代码实例:
package thread;

public class ThisLockDemo {
public static void main(String[] args) {
//Tickets0 t1 = new Tickets0();
//Tickets0 t2 = new Tickets0();
//Tickets0 t3 = new Tickets0();
//Tickets0 t4 = new Tickets0();

//第二种方法:
Tickets2 t = new Tickets2();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);

t1.start();//开启后不一定会先执行
try {
Thread.sleep(10);//线程休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
t.flag = false;
t2.start();
}
}
//方式:实现runnable接口:
class Tickets2 implements Runnable{
//定义票数:100张
private int tickets=100;
//定义标记:使用不同的锁:
boolean flag = true;
//定义锁对象:
Object obj = new Object();
@Override
public void run() {
if(flag){
while(true){//同步代码块:

synchronized(obj){
if(tickets>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":objectLock:"+tickets--);
}
}

}
}else{
while(true){
synchronized(this){
show();
}
}
}
}
//使用this锁:同步函数
public synchronized void show(){
if(tickets>0){
try{
Thread.sleep(10);//线程沉睡10纳秒,会产生中断异常
}catch(InterruptedException ie){
new InterruptedException("线程沉睡中,中断异常");
}
System.out.println(Thread.currentThread().getName()+":thisLock:"+tickets--);
}
}
}


3, 同步代码块和同步函数的区别:
同步函数使用的锁是固定的this。
同步代码块使用的锁可以是任意对象。

5,静态同步函数: 静态同步函数:
public static synchroizd void function(){//同步函数
//代码
}
静态同步函数使用的锁不是this,而是字节码文件。类名.class 6,同步的另一个弊端:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。容易产生死锁。
五,多线程间通信: 1,多线程中最为常见的应用案例:
生产者消费者问题。
生产和消费同时执行,需要多线程。
但是执行的任务却是不相同,处理的资源却是相同的,称为线程间的通信。

2,等待唤醒机制:
等待:wait();让线程处于等待状态,其实就是将线程临时存到了线程池中。
唤醒进程:notify();会唤醒线程池中任意一个等待的线程。
notifyAll();唤醒线程池中所有的等待线程。

这些方法必须使用在同步中,因为必须要表示wait,notify等方法所属的锁,同一个锁上的notify,只能唤醒该锁上的被wait的线程。以上方法被标识在object中,因为这些方法必须标识所属的锁,而锁可以是任意对象,任意对象可以调用的方法必然是object类中的方法。

3,lock接口:
lock实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的condition对象。提供了一个更加面向对象的锁,在该锁中提供了更多的锁操作。替代同步。
子类:Reentrantlock,ReentrantReadWriteLock.ReadLock,ReentrantReadWriteLock.WriteLock
方法:lock();获取锁。unlock();释放锁。  
获取监视器方法,需要先获取condition对象。condition接口对象的出现其实就是代替了object中的监视器方法。
awit();signal();signalAll();

生产者消费者实例:
package thread;

public class ProduceConcumerDemo {

public static void main(String[] args) {
Resource r = new Resource();

Producer pro = new Producer(r);
Consumer con = new Consumer(r);

Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(con);
Thread t4 = new Thread(con);
t1.start();
t2.start();
t3.start();
t4.start();
}
}

//共享资源:
class Resource{
private String name;
private int count=1;
private boolean flag = false;

public synchronized void set(String name){
while(flag){//当出现多个生产者和消费者时,必须循环判断标记
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name+"----"+(count++);
System.out.println(Thread.currentThread().getName()+"....生产者"+this.name);
flag = true;
this.notifyAll();//唤醒本方和对方:
}
public synchronized void out(){
while(!flag){//当出现多个生产者和消费者时,必须循环判断标记
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"..消费者"+this.name);
flag = false;
this.notifyAll();//唤醒本方和对方
}
}

//生产者线程:
class Producer implements Runnable{
private Resource res;

public Producer(Resource res) {
super();
this.res = res;
}
@Override
public void run() {
while(true){
res.set("+商品+");
}
}
}

//消费者线程:
class Consumer implements Runnable{
private Resource res;
Consumer(Resource res){
this.res = res;
}
@Override
public void run() {
while(true){
res.out();
}
}
}


六,多线程细节: 1,守护线程:后台线程,一般创建的都是前台线程。
前台后台线程运行时都是一样的,获取CPU的执行权执行。
只有结束的时候有些不同。
前台线程要通过run方法结束,线程才结束。
后台线程也可以通过run方法结束,线程结束,但还有另外一种情况。
当进程中所有的前台线程都结束了,这时无论后台线程处于什么样的状态,都会结束,从而线程会结束。
进程结束依赖的都是前台线程。 
通过setDeamon将线程标记为守护线程或用户线程,当正在运行的线程都是守护线程时,java虚拟机退出。
该方法必须在线程启动前调用。

2,线程的优先级:用数字标识的:1-10,
其中默认的初始优先级时5,最明显的三个优先级1,5,10.
setPriority(Thread.MAX_PRIORITY);
3,线程组:ThreadGroup:可以通过thread的构造函数明确新线程对象所属的线程组。
好处:可以对多个同组线程进行统一操作。
默认都属于main线程组。

4,线程如何停止呢?
stop停止方法已经过时。
线程结束:就是让线程任务代码执行完,run方法结束。
stop的许多使用都应由只修改某些变量以指示目标线程应该停止运行的代码来取代。目标线程应定期检查该变量,并且如果该变量指示他要停止运行则从其运行方法依次返回。若目标线程等待很长时间(例如基于一个变量),即当线程运行中处于冻结状态。则应使用interrupt方法来中断该线程。但中断并不是停止线程,而是将线程的冻结状态清除,让线程重新具备cpu的执行资格,恢复到新的运行状态。

t1.interrupt();//对t1线程对象进行中断状态的清除,强制让其恢复到运行状态。
因为是强制性的所以会有异常发生,可以在catch中捕获异常,在异常处理中,改变标记,让循环结束,让run方法结束。

5,sleep方法和wait方法的异同点:
相同点:可以让线程处于冻结状态。
不同:
1,sleep必须指定时间,wait可以指定时间,也可以不指定时间。
2,sleep时间到线程会处于临时阻塞或者运行状态,而wait如果没有指定时间必须要通过notify或者notifyall唤醒。
3,sleep不一定非要定义在同步中,而wait必须要定义在同步中。
4,都定义在同步中,线程执行到sleep不会释放锁。线程执行到wait,会释放锁。