1、多线程:
概念:
1 进程:正在运行的程序
2 线程:进程中一个程序执行的控制单元(执行路径)。
P.S.
1、一个进程中可以有多个执行路径,称之为多线程。
2、一个进程中至少要有一个线程。
3、开启多个线程是为了同时运行多部分代码,每一个线程都有自己运行的内容,这个内容可以称为线程要执
行的任务。
多线程的好处:解决多部分代码同时运行的问题。
弊端:线程太多,会导致效率的降低。
因为,多个程序同时运行,cpu需要在各个线程间进行不停的切换,这也需要消耗资源
PS:JVM启动时,至少启动了两条线程:
1 执行main函数的线程
2 垃圾回收线程
————————————————————————————————————————————————————————————————————
class Test14 {
public static void main(String[] args) {
A a = new A();
a = null;
System.gc();// garbage collection
System.out.println(Thread.currentThread().getName() + " over");
}
}
class A {
@Override
protected void finalize() throws Throwable {
System.out.println("A 的 finalize()被调用,调用进程是:"
+ Thread.currentThread().getName());
}
}
输出结果:
main over
A 的 finalize()被调用,调用进程是:Finalizer
————————————————————————————————————————————————————————————————————
2、创建线程的方式:
方法1 继承Thread类:
1 定义类继承Thread类
2 重写run方法
3 直接创建Thread类的子类,并调用start()方法启动线程
方法2 定义类实现Runnable接口
1 定义类实现Runnable接口
2 重写run方法
3 创建实现Runnable接口的对象
4 将对象作为参数传递给一个Thread类的对象,并调用该对象的start()方法启动线程
实现Runnable接口的好处:
1. 将线程的任务从线程的子类中分离出来,进行了单独的封装,更符合面向对象的思想。
2. 避免了Java单继承的局限性。 所以,创建线程的第二种方式较为常用。
PS:JVM创建的主线程的主任务定义在了主函数中,而自定义的线程其任务定义在run方法中,也就说
run方法是封装了自定义线程运行任务的函数
————————————————————————————————————————————————————————————————————
public class CreatThreadDemo {
public static void main(String[] args) {
new MyThread().start();
new Thread(new RunnableImpl()).start();
}
}
class MyThread extends Thread {
public void run() {
while (true) {
try {
Thread.sleep(200);// 父类没有抛出异常,子类调用有抛异常的方法时,只能进行catch处理
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("我是继承Thread创建的进程:"
+ Thread.currentThread().getName());
}
}
}
class RunnableImpl implements Runnable {
public void run() {
while (true) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("我是实现了Runnable创建的进程:"
+ Thread.currentThread().getName());
}
}
}
运行结果:
我是实现了Runnable创建的进程:Thread-1
我是继承Thread创建的进程:Thread-0
我是继承Thread创建的进程:Thread-0
我是实现了Runnable创建的进程:Thread-1
我是继承Thread创建的进程:Thread-0
我是实现了Runnable创建的进程:Thread-1
我是实现了Runnable创建的进程:Thread-1
————————————————————————————————————————————————————————————————————
3、线程安全:
产生原因:
1. 多个线程在操作共享的数据。
2. 操作共享数据的线程代码有多条。
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。
————————————————————————————————————————————————————————————————————
public class ThreadSafetyDemo {
public static void main(String[] args) {
final Function f = new Function();
new Thread() {
public void run() {
f.print();
};
}.start();
new Thread() {
public void run() {
f.show();
};
}.start();
}
}
class Function {
public void print() {
while (true) {
for(char c:"骚年努力~~".toCharArray())
System.out.print(c);
System.out.println();
}
}
public void show() {
while (true) {
for(char c:"Hello Java~~".toCharArray())
System.out.print(c);
System.out.println();
}
}
}
输出结果:
Hello Java~~
Hello Java~~
Hel~ //线程安全,只打印了一部分
骚年努力~~
骚年努力~lo Java~~ 线程安全,打印重叠
Hello Java~~
这里的公共资源是控制台。。。
————————————————————————————————————————————————————————————————————
4、线程安全的解决方案
1、
思路:
将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程不可以参与运
算。 必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。
在java中,用同步代码块就可以解决这个问题。
同步代码块的格式:
synchronized(对象){
需要被同步的代码;
}
同步的好处:解决了线程的安全问题。
同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程
序的运行效率。
同步的前提:必须有多个线程并使用同一个锁。
PS:为了更容易实现多个线程共同使用一个锁,最好将对公共资源操作的代码,封装成方法,并作为同一个类的成员,
然后通过run方法来调用这些操作资源的方法
2、安全问题的另一种解决方案:同步代码块
格式:在函数上加上synchronized修饰符即可。
P.S.
同步函数和同步代码块的区别:
1. 同步函数的锁是固定的this。
2. 同步代码块的锁是任意的对象。
由于同步函数的锁是固定的this,同步代码块的锁是任意的对象,那么如果同步函数和同步代码块都使用this
作为锁,就可以实现同步。
静态的同步函数使用的锁是该函数所属字节码文件对象,可以用getClass方法获取,也可以用当前类名.class
表示。
————————————————————————————————————————————————————————————————————
package ticket.demo;
public class SellDemo2 {
public static void main(String[] args) {
MyRunnable m = new MyRunnable(new TicketShared());
for (int i = 0; i < 3; i++) {
new Thread(m).start();
}
}
}
class MyRunnable implements Runnable {
private TicketShared t;
public MyRunnable(TicketShared t) {
super();
this.t = t;
}
@Override
public void run() {
while(true){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
t.dec();
}
}
}
class TicketShared {
private int t = 100;
public synchronized void dec() {
if (t > 0) {
t--;
System.out.println(Thread.currentThread().getName() + "...售出一张,剩余"
+ t);
} else {
System.out.println("票售完了~~");
System.exit(0);
}
}
}
package ticket.demo;
public class SellTicket {
/**
* 卖票程序
*/
private int j = 100;
public static void main(String[] args) {
SellTicket st = new SellTicket();
Inc inc = st.new Inc();
Dec dec = st.new Dec();
new Thread(dec).start();
new Thread(dec).start();
new Thread(inc).start();
new Thread(inc).start();
}
public synchronized void inc() {
j++;
System.out.println("加票:"+Thread.currentThread().getName() + ".."+j);
}
public synchronized void dec() {
if(j>0){
System.out.println("售票:"+Thread.currentThread().getName() + ".."+j);
j--;
}
}
class Inc implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
inc();
}
}
}
class Dec implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
dec();
}
}
}
}
————————————————————————————————————————————————————————————————————
5、单利设计模式
//单利设计模式,单利即只能有一个实例,所以构造函数要私有化
// 饿汉式:类初始化时就创建实例对象,线程安全
class SingletonHungryType {
private static final SingletonHungryType s = new SingletonHungryType();// 类初始化时就创建对象
private SingletonHungryType() {
}// 私有化,以实现单利
public static SingletonHungryType getInstance() {// 返回实例对象
return s;
}
}
// 懒汉式:类初始化时不创建对象,等函数调用时,才创建。因为涉及可能存在多个线程同时调用创建对象的代码,所以线程不安全,需要实现同步
class SingletonLazyType1 { // 同步代码块实现
private static SingletonLazyType1 s = null;
private SingletonLazyType1() {
}
public static SingletonLazyType1 getInstance() {
if (s == null) {// 在这加判断能提高效率,因为当s已经建立后,线程可以直接跳过同步代码块,而不需要没次都等待锁
// 这也就是使用同步代码块比同步函数效率高的原因
synchronized (SingletonLazyType1.class) {
if (null == s) {
s = new SingletonLazyType1();
}
}
}
return s;
}
}
class SingletonLazyType2 { // 同步函数实现
private static SingletonLazyType2 s = null;
private SingletonLazyType2() {
}
public static synchronized SingletonLazyType2 getInstance() {
s = new SingletonLazyType2();
return s;
}
}
6、死锁
/**
* 死锁演示
*/
public class DeadLock implements Runnable {
private Object objA = new Object();
private Object objB = new Object();//锁对象
private boolean flag = false;
// DeadLock(boolean b){
// this.flag = b;
// }
@Override
public void run() {
// 相互嵌套是锁,可能会出现互相等待的现象导致死锁
while(true){
if(flag){
synchronized(objA){//嵌套锁
System.out.println("if A..."+Thread.currentThread().getName());
synchronized(objB){
System.out.println("if B..."+Thread.currentThread().getName());
}
flag = flag^true; //flag取反
}
}else{
synchronized(objB){//嵌套锁
System.out.println("else A..."+Thread.currentThread().getName());
synchronized(objA){
System.out.println("else B..."+Thread.currentThread().getName());
}
flag = flag^true; //flag取反
}
}
}
}
}
public class DeadLockTest {
/**
* 用于死锁的测试
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
//建立资源对象
DeadLock d1 = new DeadLock();
//新建两个进程
new Thread(d1,"线程1").start();
new Thread(d1,"线程2").start();
}
}
运行结果:
else A...线程1 //线程1 进入else,获取锁B
else B...线程1
if A...线程1 //线程1执行完else,释放B锁,进入if,获取锁A,在等待获取B锁
else A...线程2 //线程2 持有B锁,在等待A锁
//此时出现互相等待,即死锁现象,程序无法继续运行
————————————————————————————————————————————————————————————————————
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//死锁演示,出现嵌套锁时会出现互相等待对方释放锁资源现象
public class DeadLockDemo {
public static void main(String[] args) {
final DeadLock dl = new DeadLock();
new Thread() {
public void run() {
while (true)
dl.show();
};
}.start();
new Thread() {
public void run() {
while (true)
dl.print();
};
}.start();
}
}
class DeadLock {
private Lock lock1 = new ReentrantLock();
private Lock lock2 = new ReentrantLock();
public void show() {
lock1.lock();
System.out.println(Thread.currentThread().getName()+"..show...lock1.lock();");
lock2.lock();
System.out.println(Thread.currentThread().getName()+"..show...lock2.lock();");
lock2.unlock();
lock1.unlock();
}
public void print() {
lock2.lock();
System.out.println(Thread.currentThread().getName()+"..print...lock2.lock();");
lock1.lock();
System.out.println(Thread.currentThread().getName()+"..ptint...lock1.lock();");
lock1.unlock();
lock2.unlock();
}
}
运行:
Thread-0..show...lock1.lock();
Thread-0..show...lock2.lock();
Thread-0..show...lock1.lock();
Thread-1..print...lock2.lock();
————————————————————————————————————————————————————————————————————
7、线程间通讯
多个线程在处理统一资源,但是任务却不同,这时候就需要线程间通信。
等待/唤醒机制涉及的方法:
1. wait():让线程处于冻结状态,被wait的线程会被存储到线程池中。
2. notify():唤醒线程池中的一个线程(任何一个都有可能)。
3. notifyAll():唤醒线程池中的所有线程。
P.S.
1、这些方法都必须定义在同步中,因为这些方法是用于操作线程状态的方法。
2、必须要明确到底操作的是哪个锁上的线程!
3、wait和sleep区别?
1)wait可以指定时间也可以不指定。sleep必须指定时间。
2)在同步中时,对CPU的执行权和锁的处理不同。
wait:释放执行权,释放锁。
sleep:释放执行权,不释放锁。
同步代码块就是对于锁的操作是隐式的。
JDK1.5以后将同步和锁封装成了对象,并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了
显示动作。
Lock接口:出现替代了同步代码块或者同步函数,将同步的隐式操作变成显示锁操作。同时更为灵
活,可以一个锁上加上多组监视器。
lock():获取锁。
unlock():释放锁,为了防止异常出现,导致锁无法被关闭,所以锁的关闭动作要放在finally中。
Condition接口:出现替代了Object中的wait、notify、notifyAll方法。将这些监视器方法单独进行了
封装,变成Condition监视器对象,可以任意锁进行组合。
Condition接口中的await方法对应于Object中的wait方法。
Condition接口中的signal方法对应于Object中的notify方法。
Condition接口中的signalAll方法对应于Object中的notifyAll方法。
8、停止线程
怎么控制线程的任务结束呢?
任务中都会有循环结构,只要控制住循环就可以结束任务。
控制循环通常就用定义标记来完成
P.S.
也可以使用stop方法停止线程,不过已经过时,不再使用。
但是如果线程处于了冻结状态,无法读取标记,如何结束呢?
答:可以使用interrupt()方法将线程从冻结状态强制恢复到运行状态中来,让线程具备CPU的执行资格。
强制动作会发生InterruptedException,一定要记得处理。