1 多线程
1.1 进程:
进程就是一个正在执行中的程序。
每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。
1.2 线程:
线程就是进程中的一个独立的控制单元。
线程在控制着进程的执行,一个进程中至少有一个线程。
1.3 主线程:
Java虚拟机(JVM)启动的时候会有一个进程java.exe,该进程中至少有一个线程负责java程序的执行,而且这个线程运行的代码存在于main方法中,该线程称之为主线程。
其实JVM启动不止一个线程,还有负责垃圾回收机制的线程。
1.4 创建线程:
通过对API的查找,Java已经提供了对线程这类事物的描述,就是Thread类。
创建线程的第一种方式:继承Thread类,覆run()方法。
步骤:1,自定义类,并继承Thread类。
2、重写Thread类中的run()方法。
目的:将定义的代码存储在run()方法中,让线程运行。
3、调用线程的start()方法。
start()方法有两个作用:启动线程,调用run()方法。
代码示例如下:
class Demo extends Thread { //要创建线程必须继承Thread类
public void run(){
for(int x=0;x<120;x++)
System.out.println("Demo run--"+x);
}
}
class ThreadDemo{
public static void main(String[] args){
Demo d = new Demo(); //创建好一个线程
d.start(); //启动线程,并调用run()方法。
//d.run(); //这样,仅仅是对象调用方法,而线程创建了,并没有运行。
for(int x=0;x<120;x++)
System.out.println("Hello World--"+x);
}
}
发现运行结果每一次都不同。
因为多个线程都获取CPU执行权,CPU执行到谁,谁就运行。Demo线程和主线程争夺CPU。
明确一点,在某一个时刻,只能有一个程序在运行(多核除外)。
CPU在做着快速切换,以达到看上去是同时运行的效果。
我们可以形象的把多线程的运行形容为在互相抢夺CPU的执行权(CPU资源);
这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长时间,CPU说的算。
为什么要覆盖run()方法呢?
Thread类用于描述线程;该类就定义了一个功能,用于存储线程要运行的代码,该存储功能就是run()方法。也就是说Thread类中的run()方法,用于存储线程要执行的代码。
线程都有自己默认的名称:
Thread-编号,该编号从0开始。
currentThread(); 该方法获取当前线程对象;
getName(); 该方法获取线程名称
设置线程名称:serName()或者构造函数。
1.5 简单的卖票程序:
需求:简单的卖票程序,多个窗口同时卖票。
创建线程的第二种方式:实现Runnable接口。
步骤:1、定义类实现Runnable接口。
2、覆盖Runnable接口中的run()方法。
将线程要运行的代码存放在该run()方法中。
3、通过Thread类建立线程对象。
4、将Runnable接口的子类对象作为实际参数,传递给Thread类的构造函数。
为什么要将Runnable接口的子类对象传递给Thread的构造函数:
因为,自定义的run()方法所属的对象是Runnable接口的子类对象。
所以要让线程去执行指定对象的run()方法,就必须明确该run()方法所属的对象。
5、调用Thread类的start()方法,启动线程,并调用Runnable接口子类的run方法。
第一种和第二种,即实现Runnable接口方式和继承Thread类方式有什么区别?
实现方式的好处:避免了单继承的局限性。
在定义线程时,建议使用实现方式。
两种方式的区别:
继承Thread类:线程代码存放在Thread子类的run()方法中。
实现Runnable接口:线程代码存放在接口的子类的run()方法中。
多线程的安全问题:
问题的原因:当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,这时另一个线程参与进来执行,导致了共享数据的错误。
解决办法:对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
Java对于多线程的安全问题提供了专业的解决方式:同步代码块和同步函数。
同步代码块:
synchronized(对象)
{
需要被同步的代码; //即操作共享数据的代码,是需要被同步的代码
}
对象如同锁,持有锁的线程可以在同步中执行。
没有持有锁的线程即使获取CPU的执行权,也进不去,因为没有获取锁。
同步的前提:
1、必须要有两个或两个以上的线程。锁住操作共享数据的代码。
2、必须是多个线程使用同一个锁(即同一个对象),必须保证同步中只能有一个线程在运行。
好处:解决了多线程的安全问题
弊端:多个线程都需要判断锁,较为消耗资源。
卖票程序代码:
class Ticket implements Runnable {//extends Thread {
private int tick = 100;
Object obj = new Object(); //解决安全问题
public void run() {
while(true) {
synchronized(obj){ //同步代码块,解决安全问题
if(tick>0) {
try{Thread.sleep(10);}catch(Exception e){} //sleep()抛出一个异常。出现-1、-2,出现安全问题
System.out.println(Thread.currentThread().getName()+"sale: "+tick--);
}
}
}
}
}
class ThreadDemo2 {
public static void main(String[] args){
Ticket t = new Ticket(); //t不是线程,因为与Thread类无关
Thread t1 = new Thread(t); //创建一个线程,并把Runnable子类对象传递给Thread构造函数
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
System.out.println("---main---");
}
}
1.6 同步函数:
把synchronized作为修饰符放在函数声明中,此函数就具有同步功能(相当于同步代码块)。
这个被synchronized修饰的函数就是同步函数。
同步函数用的哪一个锁呢?
函数需要被对象调用,那么函数都有一个所属对象的引用,就是this。
所以同步函数使用的同步锁是this。
牢记同步的两个前提,如果加同步后还有问题,就查看是否满足同步到前提。
静态同步函数:
如果同步函数被静态修饰后,使用的锁是什么呢?
通过验证,发现不再是this,因为静态方法中也不可以定义this。
静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。getClass()方法
类名.class 该对象的类型是class
静态的同步方法,使用的同步锁是该方法所在类的字节码文件对象:类名.class
需求:银行有一个金库,有两个储户分别存300元,每次存100,存三次。
目的:该程序是否有安全问题,如果有,如何解决?
如何找问题(需要同步的代码怎么找):
1、明确哪些代码是多线程运行代码。
2、明确共享数据。
3、明确多线程运行代码中哪些语句是操作共享数据的。
代码:
class Bank {
private int sum;
//Object obj = new Object();
public synchronized void add(int n){ //用synchronized修饰,add为同步函数
//synchronized(obj){
sum = sum + n;
try{Thread.sleep(10);} catch(Exception e){}
System.out.println("sum="+sum);
//}
}
}
class Cus implements Runnable {
private Bank b = new Bank();
public void run() {
for(int x=0; x<3; x++) {
b.add(100);
}
}
}
class ThreadDemo3 {
public static void main(String[] args) {
Cus c = new Cus();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
2 单例设计模式
设计模式:解决某一类问题最行之有效的方法。
单例设计模式:一个类在内存只存在一个对象,想要保证对象唯一。
1、为了避免其他程序过多建立该类对象,先禁止其他程序建立该类对象
2、还为了让其他程序可以访问到该类对象,只好在本类中,自定义一个对象。
3、为了方便其他程序对自定义对象的访问,可以对外提供一些访问方式。
这三部分怎么用代码体现呢?
1、将构造函数私有化
2、在类中创建一个本类对象
3、提供一个方法可以获取到该对象。
对于事物该怎么描述,还怎么描述。
当需要将该事物的对象保证在内存中唯一时,就将以上的三步加上即可。
2.1 饿汉式 :
饿汉式:先初始化对象。
Single类一进内存,就已经创建好了对象。
class Single {
private Single(){}
private static final Single s = new Single();
public static Single getInstance() {
return s;
}
}
2.2 懒汉式
懒汉式:对象是方法被调用时,才初始化,也叫做对象的延时加载。
Single类进内存,对象还没有存在,只有调用了getInstance()方法时,才建立对象。
class Single {
private Single(){}
private static Single s = null;
public static Single getInstance() {
if(s==null)
s = new Single();
return s;
}
}
代码示例:
class Single { //懒汉式延迟加载,会出现多线程安全问题,用同步锁解决
private Single(){}
private static Single s = null;
public static Single getInstance() { //静态函数中,同步锁使用本类字节码
if(s==null) {
synchronized(Single.class) { //同步锁是该类所属的字节码文件对象
if(s==null) //同步会降低执行效率
s = new Single();
}
}
return s;
}
}
class SingleDemo {
public static void main(String[] args){
System.out.println(Single.getInstance().getClass());
}
}
3 死锁
死锁:同步中嵌套同步,会发生死锁。
比如,A锁的同步代码中需要B锁,而B锁的同步代码中需要A锁,
就会产生冲突,发生死锁。
代码示例:
class Ticket implements Runnable {
private int tick = 1000;
Object obj = new Object();
boolean flag = true;
public void run() {
if(flag) {
while(true) {
synchronized(obj) { //obj锁
show(); //需要this锁,而此时this锁被使用
} //同步中嵌套同步,出现死锁
}
}
else
while(true)
show();
}
public synchronized void show() { //this锁
synchronized(obj) { //需要obj锁,而此时obj锁被使用
if(tick > 0) {
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"....code: "+tick--);
}
}
}
}
class DeadLockDemo {
public static void main(String[] args) {
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
try{Thread.sleep(10);}catch(Exception e){}
t.flag = false;
t2.start();
}
}
小练习:写一个死锁程序。
提示:多线程,同步锁,死锁。
class Test implements Runnable {
private boolean flag;
Test(boolean flag){
this.flag = flag;
}
public void run() {
if(flag){
synchronized(MyLock.locka){ //使用a锁
System.out.println("if locka");
synchronized(MyLock.lockb){ //需要b锁,而此时b锁在被使用
System.out.println("if lockb");
}
}
}
else{
synchronized(MyLock.lockb){ //使用b锁
System.out.println("else lockb");
synchronized(MyLock.locka){ //需要a锁,而此时a锁在被使用
System.out.println("else locka");
}
}
}
}
}
class MyLock {
static Object locka = new Object();
static Object lockb = new Object();
}
class DeadLockTest{
public static void main(String[] args){
Thread t1 = new Thread(new Test(true));
Thread t2 = new Thread(new Test(false));
t1.start();
t2.start();
}
}