进程:正在进行中的程序(直译)。
进程其实对应一个应用程序在内存中的空间。
线程:就是进程中一个负责程序执行的执行路径
一个进程中可以有多个执行路径,称之为多线程。
一个进程中至少要有一个线程。
开启多个线程是为了同时运行多部分代码。
每一个线程都有自己运行的内容。这个内容可以称为线程要执行的任务。
多线程的好处与弊端:
好处:解决了多部分同时运行的问题。
弊端:线程太多 效率降低。
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 Demo2 implements Runnable{
public void run(){
show();
}
public void show(){
for (int i = 0; i < 15; i++) {
System.out.println(Thread.currentThread().getName()+"..."+ i );
}
}
}
public class RunnableDemo2 {
public static void main(String[] args) {
Demo2 d1 = new Demo2();
Demo2 d2 = new Demo2();
Thread t1 = new Thread(d1);
Thread t2 = new Thread(d2);
t1.start();
t2.start();
}
}
---------------------------------------------------
线程安全问题产生的原因:
1.多个线程在操作共享的数据。
2.操作共享数据的线程代码有多条。
当一个线程在指向操作共享数据的多条代码过程中,其他线程参与了运算,
就会导致线程安全问题。
解决思路:
就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,
其他线程不可以参与运算。
必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。
在java中,用同步代码块就可以解决这个问题。
同步代码块的格式:
synchronized(对象){
需要被同步的代码;
}
同步的好处和弊端。
同步的好处:解决了线程的安全问题。
同步的弊端:相对降低了程序的效率,因为同步外的线程都会判断同步锁。
同步的前提:
同步中必须有多个线程,并使用同一个锁。
同步函数的锁是this.
同步函数和同步代码块的区别:
同步函数的锁是固定的this。
同步代码块的锁是任意的对象。
建议使用同步代码块。
静态的同步函数使用的锁是:该函数所属的字节码文件对象。
可以用getClass获取,也可以用 当前类名.class表示。
例如:Class clazz1 = t.getClass();
Class clazz2 = Ticket2.class;
class Ticket2 implements Runnable {
private int num = 100;
Object obj = new Object();
boolean flag = true;
public void run(){
if(flag)
while(true){
synchronized(this){//Ticket2.class或者this.getClass()
if(num>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName()+"...code..."+num--);
}
}
}
else
while(true)
show();
}
public synchronized void show(){
if(num>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName()+"...func..."+num--);
}
}
}
public class SynchroinzedFunctionLockDemo5 {
public static void main(String[] args) {
Ticket2 t = new Ticket2();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
t.flag = false;
t2.start();
}
}
---------------------------------------------------
死锁:常见情景之一:同步的嵌套。同步中还有同步。(面试中可能会出现)
class Test implements Runnable{
private boolean flag;
Test(boolean flag){
this.flag = flag;
}
public void run(){
if(flag){
while(true){
synchronized(MYlock.locka){
System.out.println("if...locka...");
synchronized(MYlock.lockb){
System.out.println("if...lockb...");
}
}
}
}else{
while(true){
synchronized(MYlock.lockb){
System.out.println("else...lockb...");
synchronized(MYlock.locka){
System.out.println("else...locka...");
}
}
}
}
}
}
class MYlock{
public static final Object locka = new Object();
public static final Object lockb = new Object();
}
public class DeadLockDemo8 {
public static void main(String[] args) {
Test a = new Test(true);
Test b = new Test(false);
Thread t1 = new Thread(a);
Thread t2 = new Thread(b);
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 解决了本方线程一定会唤醒对方线程的问题。
class Resource2{
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){}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName()+"...生产者"+this.name);
flag = true;
notifyAll();
}
public synchronized void get(){
while(!flag)
try{this.wait();}
catch(InterruptedException e){}
System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
flag = false;
notifyAll();
}
}
class Producer implements Runnable{
Resource2 r;
Producer(Resource2 r){
this.r = r;
}
public void run(){
while(true){
r.set("烤鸭");
}
}
}
class Consumer implements Runnable{
Resource2 r;
Consumer(Resource2 r){
this.r = r;
}
public void run(){
while(true){
r.get();
}
}
}
public class ConsumerDemo2 {
public static void main(String[] args) {
Resource2 r = new Resource2();
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();
}
}
-----------------------------------------------------------------
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;
class Resource3{
private String name;
private int count = 1;
private boolean flag = false;
//创建一个锁对象。
Lock lock = new ReentrantLock();
//通过已有的锁获取该锁上的监视器对象。
// Condition c = 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()+"...生产者"+this.name);
flag = true;
consumer_con.signal();
}
finally{
lock.unlock();
}
}
public void get(){
lock.lock();
try{
while(!flag)
try{consumer_con.await();}
catch(InterruptedException e){}
System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
flag = false;
producer_con.signal();
}
finally{
lock.unlock();
}
}
}
class Producer2 implements Runnable{
Resource3 r;
Producer2(Resource3 r){
this.r = r;
}
public void run(){
while(true){
r.set("烤鸭");
}
}
}
class Consumer2 implements Runnable{
Resource3 r;
Consumer2(Resource3 r){
this.r = r;
}
public void run(){
while(true){
r.get();
}
}
}
public class NewJDKDemo3 {
public static void main(String[] args) {
Resource3 r = new Resource3();
Producer2 pro = new Producer2(r);
Consumer2 con = new Consumer2(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();
}
}
-----------------------------------
多线程中的一点小细节:
wait和sleep的区别。
1.wait可以指定之时间也可以不指定。
sleep必须指定之间。(sleep一定会醒,wait不一定会醒)
2.在同步中时,对于cpu的执行权和锁的处理不同。
wait:释放执行权,释放锁。
sleep:释放执行权,不释放锁。
同步中可以有多条线程,但是只有一个线程在执行,谁拿锁谁执行。
停止线程:
1.stop方法(已过时)。
2.run方法结束。
怎么控制线程的任务结束呢?
任务中都会有循环结构,只要控制住循环就可以结束任务。
控制循环通常就用定义标记来完成。
但是如果线程处于冻结状态,无法读取标记,如何结束呢?
可以使用interrupt();方法将线程从冻结状态强制恢复到运行状态中来,
让线程具备cpu的执行资格。
但是强制动作会发生InterruptedException异常,记得要处理。
setDaemon()当正在运行的线程都是守护线程时,虚拟机退出。
如果所有的前台线程都结束,后台线程无论处于何种状态都会自动结束。
临时加入一个线程运算是,可以使join方法。
t1.join();//t1线程要申请加入进来运行。主线程会释放执行资格(冻结状态),t1结束后再执行。
哪个线程执行到join方法,哪个线程会释放执行资格,处于冻结状态,
join 线程结束后恢复。
toString()线程的字符串表现形式。
线程优先级在1到10之间10个数。
优先级越大被执行到的几率越大。
t1.setPriority(Thread.MAX_PRIORITY);设置线程t1的优先级为10.
默认优先级是5
线程组:ThreadGroup
yield()方法 可以使线程暂停,暂时释放执行权。