-Java编程知识点总结。
一.多线程。
1. 什么是进程:进程是正在运行的程序。2.运行中的电脑看上去可以同时执行多个程序其实CPU同一时刻只能运行一个程序,之所以看上去是在同时运行,是因为CPU在不停的切换执行。每一个进程执行都一个执行顺序,该顺序是一个执行路径或者叫一个执行单元。
2. 什么是线程:线程就是进程中的一个独立的单元,线程在控制进程的执行。
3. jvm启动的时候会有一个进程java.exe。该进程至少有一个线程负责java程序的执行,而且这个线程运行的代码存在于main方法中。该线程称作为主线程。
二.自定义线程。
Java已经提供了对线程这类食物的描述。Thread类。
1. 创建线程的第一种方式:继承Thread类方法。
1,定义类继承Thread。
2,复写Thread类中的run方法。目的:将自定义代码存储在run 方法。让线程运行。
3,调用线程的start方法,该方法两个作用:启动线程,调用run方法。
class Demoextends Thread
{
public void run ()
{
for(int i = 0;i<=60;i++)
{
System.out.println(i);
}
}
}
classThreadDmo
{
public static void main(String[] args)
{
Demo d = new Demo();//创建继承自Thread类的子类对象。
d.start();//start作用是开启线程,并调用run()方法。
for(int x=0; x<60; x++)
System.out.println("HelloWorld!--"+x);
}}
练习一:
创建两个线程,和主线程交替运行。线程都有自己默认的名称,Thread-编号的形式,编号是从0开始。Thread-0;
获取当前线程对象:static Thread currentThread();
设置线程的名称:setName();
class Testextends Thread
{
//private String name;
Test(String name)
{
//this.name = name;
super(name);//调用父构造函数,将对象名称传给父。
}
public void run()
{
for(int x=0; x<60; x++)
//获取当前线程的对象获取当前线程对象的名称。
//该方法写于run方法中,即写于当前线程中使用。
//那个线程运行当前的run,就获得谁的对象t1,或t2。
{System.out.println((Thread.currentThread()==this)+"..."+this.getName()+"run..."+x);
}
}
}
class ThreadTest
{
public static void main(String[] args)
{
Test t1 = new Test("one---");
Test t2 = new Test("two+++");
t1.start();
t2.start();
for(int x=0; x<60; x++)
{
System.out.println("main....."+x);
}
}
}
2.多线程状态图:
3.(必须掌握)练习二:售票。
class Tickets extends Thread
{privateint ticket = 100;
Tickets(String name)
{
super(name);//必须会使用,1.使用构造函数2.调用父构造3.传对象的名称。
}
publicvoid run()
{
while (ticket>0)
{
ticket-=1;
System.out.println(Thread.currentThread()+"sales"+ticket+this.getName());
}
}
}
class TicketDemo
{
publicstatic void main(String[] args)
{
Ticketst1 = new Tickets("线程1");
Ticketst2 = new Tickets("线程2");
Ticketst3 = new Tickets("线程3");
Ticketst4 = new Tickets("线程4");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
2.创建线程的第二种方式。
1.实现Runnable接口。
2.重写run()方法。
3.(*)通过Thread类创建线程对象。
4.使用Thread类创建线程对象。将实现自Runnable接口的子类对象作为参数传递给Thread类的构造方法。因为自定义对象的run方法是Runnable接口的抽象方法run方法是子类的方法。而真正能使run方法运行的是Thread类所以要让线程运行run就得把Runnable的子类对象传递给Thread。
5.调用Thread类的start方法开启线程并调用Runnable接口。
6.实现方式和继承方式的区别:使用Runnable接口可以实现多继承,即当继承了Thread类,子类就不能再继承其他类。当实现自Runnable接口,子类即创建了线程,又可以再继承其它的类。
5多线程安全问题
多线程安全问题出现的原因:
当多条语句在操作同一个线程的共享数据时,一个线程对多条语句执行了一部分还没执行完,另一个线程参与进行执行,导致共享数据错误。
如下所示:
class Tickets extends Thread
{privateint ticket = 100;//该数据是4个线程共享的数据
Tickets(String name)
{
super(name);
}
//run方法是4个线程共同执行的方法。存在于每个线程中,不是共享的。X定义在函数中属于局部是非共享,ticket是定义在类中的是共享的
publicvoid run() //throws RuntimeException子类只能抛父类的子集,父类没有抛。所以不能抛出。
{intx;
while (ticket>0)
{ try
{
Thread.sleep(50);//throwsInterruptedException该方法在使用时自动抛出异常,所以要处理。
//有可能出现以下情况1.2.3.4线程。当ticket = 1时,1线程获取执行权,打印1;然后自减为0。
//这时2线程获得了执行权,又打印了一次0,自减为-1.这时3线程执行,又打印了-1.这些线程就没有进行条件判断,在这里输出了错值。
System.out.println(this.getName()+"sales"+ticket--);
}
catch (Exception e)
{
}
}
}
}
class TicketDemo
{
publicstatic void main(String[] args)
{
Ticketst1 = new Tickets("线程1");
Ticketst2 = new Tickets("线程2");
Ticketst3 = new Tickets("线程3");
Ticketst4 = new Tickets("线程4");
t1.start();
t2.start();
t3.start();
t4.start();
}
1.(*)解决办法对多条操作共享数据的语句只让一个线程执行完,其他线程都不可以参与。使用同步代码块。
synchronized(对象)
{
需要被同步的代码
}对象如同锁。持有锁的线程可以在同步中执行。
没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。
把上个例子的代码修改为安全的代码:
Object obj = new Object();
public void run()
{
//锁就是对象。当线程t1.t2执行到这时,
// 1.假如t1获得了锁,锁标0->1;
4.t2获得了执行权,t2进入,执行输出语句。
Synchronized(obj) 锁标(0)禁止(1)进入
{//2.t1线程进入后锁标1->0;t2进不来。
while (ticket>0)
{
try
{
Thread.sleep(50);//线程会在这儿冻结,
当时间到后,转入到阻塞状态等cpu执行权。
System.out.println(this.getName()+"sales"+ticket--);
}
catch (Exception e)
{
}
}
//3.当t1执行完后退出0->1;t1跳出锁。
}
}
在锁标处的执行情况:
1.假如t1持有了锁,锁标0->1;
2.t1线程进入后锁标1->0;t2进不来。
3.当t1执行完后退出0->1;t1跳出锁。
4.此时可进入,t2获得了执行权,t2进入,执行输出语句。
总结:对象如同锁。持有锁的线程可以在同步中执行。没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。
同步的前提:
1,必须要有两个或者两个以上的线程。
2,必须是多个线程使用同一个锁。(同一个对象)
3.必须保证同步中只能有一个线程在运行。
好处:解决了多线程的安全问题。
弊端:多个线程需要判断锁,较为消耗资源,
1. (*)同步函数:使函数具有同步性。在函数的返回类型前加上synchronized.所以到这里同步方法有两种:
1.同步函数:使用synchronized作为修饰放在返回类型的前边。 2.同步代码块,使用synchroized(对象){}.的方式实现同步。
如何找问题:
1,明确哪些代码是多线程运行代码。
2,明确共享数据。
3,明确多线程运行代码中哪些语句是操作共享数据的
class Bank
{
privateint sum;//sum是共享数据所以要把操作共享数据的语句封装为同步语句。
//Objectobj = new Object();
publicsynchronized void add(int n)
{
//synchronized(obj)
//{
sum= sum + n;
try{Thread.sleep(10);}catch(Exceptione){}
System.out.println("sum="+sum);
//}
}
}
class Cus implements Runnable
{
privateBank b = new Bank();
publicvoid run()
{
for(intx=0; x<3; x++)
{
b.add(100);//调用了包含共享数据的方法。
}
}
}
class BankDemo
{
publicstatic void main(String[] args)
{
Cusc = new Cus();
Threadt1 = new Thread(c);
Threadt2 = new Thread(c);
t1.start();
t2.start();
}
}
练习:使用同步函数的方式重写售票的例子。
2. 同步函数的锁。
同步函数用的是哪一个锁呢?函数需要被对象调用。那么函数都有一个所属对象引用。就是this。所以同步函数使用的锁是this。
下面的实例来验证上面的结论:
通过该程序进行验证。使用两个线程来买票。一个线程在同步代码块中。一个线程在同步函数中。
都在执行买票动作。
class Ticket implements Runnable
{
private int tick = 100;
Objectobj = new Object();
booleanflag = true;
public void run()
{
if(flag)
{
while(true)
{//当锁使用obj时,线程会出现打印错误的票数,表明线程出现了不同步的情况,当时用this时,打印正确。表明this时同步函数的锁。
synchronized(this)
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exceptione){}
System.out.println(Thread.currentThread().getName()+"....code: "+ tick--);
}
}
}
}
else
while(true)
show();
}
publicsynchronized void show()//this
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exceptione){}
System.out.println(Thread.currentThread().getName()+"....show....: "+ tick--);
}
}
}
class ThisLockDemo
{
publicstatic void main(String[] args)
{
Tickett = new Ticket();
//两个线程操作的是同一个对象。共享数据是ticket
Threadt1 = new Thread(t);
Threadt2 = new Thread(t);
t1.start();
try{Thread.sleep(10);}catch(Exceptione){}
t.flag= false;
t2.start();
}
}
3. 静态同步函数的锁
静态进内存是,内存中没有本类对象,但是一定有该类对应的字节码文件对象。类名.class该对象的类型是Class。
class Ticket implements Runnable
{
privatestatic int tick = 100;
//Objectobj = new Object();
booleanflag = true;
public void run()
{
if(flag)
{
while(true)
{
synchronized(Ticket.class)
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exceptione){}
System.out.println(Thread.currentThread().getName()+"....code: "+ tick--);
}
}
}
}
else
while(true)
show();
}
publicstatic synchronized void show()
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exceptione){}
System.out.println(Thread.currentThread().getName()+"....show....: "+ tick--);
}
}
}
class StaticMethodDemo
{
publicstatic void main(String[] args)
{
Tickett = new Ticket();
Threadt1 = new Thread(t);
Threadt2 = new Thread(t);
t1.start();
try{Thread.sleep(10);}catch(Exceptione){}
t.flag= false;
t2.start();
}
}
静态的同步方法,使用的锁是该方法所在类的字节码文件对象。 类名.class
4. 单例设计模式。
class Single
{
privatestatic Single s = null;
privateSingle(){}
publicstatic Single getInstance()
{
if(s==null)
{
synchronized(Single.class)
{
if(s==null)
s= new Single();
}
}
returns;
}
}
class SingleDemo
{
publicstatic void main(String[] args)
{
System.out.println("HelloWorld!");
}
}
2. 多线程死锁。
class Test implements Runnable
{
privateboolean flag;
Test(booleanflag)
{
this.flag= flag;
}
publicvoid run()
{
if(flag)
{
while(true)
{//假如t1有a锁并进入
synchronized(MyLock.locka)
{
System.out.println(Thread.currentThread().getName()+"...iflocka ");//t1有a但b锁被t2拥有,只有t2执行完下面的语句才会释放b锁,所以t1停止这里。
synchronized(MyLock.lockb)
{
System.out.println(Thread.currentThread().getName()+"..iflockb");
}
}
}
}
else
{
while(true)
{//假如t1阻塞,t2执行,t2有b锁进行
synchronized(MyLock.lockb)
{
System.out.println(Thread.currentThread().getName()+"..elselockb"); 同理t2也被挂在这里。因为t2没有a锁。
synchronized(MyLock.locka)
{
System.out.println(Thread.currentThread().getName()+".....elselocka");
}
}
}
}
}
}
class MyLock
{
staticObject locka = new Object();
staticObject lockb = new Object();
}
class DeadLockTest
{
publicstatic void main(String[] args)
{
Threadt1 = new Thread(new Test(true));
Threadt2 = new Thread(new Test(false));
t1.start();
t2.start();
}
}
3. (*)线程间通信。
当前的线程对象持有的锁对象调用:
wait():当前持有锁的线程对象处于了冻结状
notify():唤醒线程池中首个被冻结的线程。
notifyAll();唤醒线程池中所有的被冻结的线程。
它们都使用在同步中,因为要对持有监视器(锁)的线程操作。所以要使用在同步中,因为只有同步才具有锁。为什么这些操作线程的方法要定义Object类中呢?因为这些方法在操作同步中线程时,都必须要标识它们所操作线程只有的锁,只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒。
不可以对不同锁中的线程进行唤醒。也就是说,等待和唤醒必须是同一个锁。
而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。
(因为要被当做锁的对象不确定,即所有对象都要有notify和wait的功能,既然所有类型的对象都要具备这两个功能,那只能定义在Object(上帝)中了,因为所有类都继承自Object)。
1. 示例。
class Res
{
Stringname;
Stringsex;
booleanflag = false;
}
class Input implements Runnable
{
privateRes r ;
Input(Resr)
{
this.r= r;
}
publicvoid run()//input中的run方法
{
intx = 0;
while(true)
{
synchronized(r)
{
if(r.flag)
try{r.wait();}catch(Exceptione){}
if(x==0)
{
r.name="mike";
r.sex="man";
}
else
{
r.name="丽丽";
r.sex= "女女女女女";
}
x = (x+1)%2;
r.flag= true;
r.notify();
}
}
}
}
class Output implements Runnable
{
privateRes r ;
Output(Resr)
{
this.r= r;
}
publicvoid run()
{
while(true)
{
synchronized(r)
{
if(!r.flag)
try{r.wait();}catch(Exceptione){}
System.out.println(r.name+"...."+r.sex);
r.flag= false;
r.notify();
}
}
}
}
class InputOutputDemo
{
publicstatic void main(String[] args)
{
Resr = new Res();
Inputin = new Input(r);
Outputout = new Output(r);
Threadt1 = new Thread(in);
Threadt2 = new Thread(out);
t1.start();
t2.start();
}
}
以上代码的执行过程:
1.创建一个公共数据类Res,创建两个实现Runnable接口的线程对象。
2.创建两个线程对象,将对象传给创建线程的Thread对象用于执行run方法。
3.启动线程t1.t2。t1和t2的run方法不同,但两者操作的都是同一个对象的属性,即共享属性,但是操作的动作不同。这就被称为线程之间的通信。
4.t1线程先被启动,执行in中的run方法。一,第一次执行。二,第二次执行。
publicvoid run()//input中的run方法
{
intx = 0;
while(true)
{//同步前提:1.多线程。2.锁相同。
synchronized(r)//两者使用的是同一个锁
{
if(r.flag)//一1.flag = false;不执行一6.flag =true
//一.7执行冻结当前线程wait()会自动抛异常,所以需要处理。
try{r.wait();}catch(Exceptione){}
if(x==0)一2.x=0执行改变name,sex。
{
r.name="mike";
r.sex="man";
}
else
{
r.name="丽丽";
r.sex= "女女女女女";
}
x= (x+1)%2;一3.x=-1;
r.flag= true;一4.flag = true;
r.notify();一5.唤醒output线程即t2线程开启。
}
}
t2线程被启动,执行out中的run方法。一,第一次执行。二,第二次执行。
class Output implements Runnable
{
privateRes r ;
Output(Resr)
{
this.r= r;
}
publicvoid run()
{
while(true)
{
synchronized(r)
{
//二1.flag = true不执行。
if(!r.flag)// 二5.t2进入冻结状态。 try{r.wait();}catch(Exception e){}
//二2.输出被in修改的name和sex
System.out.println(r.name+"...."+r.sex);
r.flag= false;//二3.flat =false;
r.notify();//二4.唤醒in线程,即t1线程。
}
}
}
}
当t1被唤醒时,t1又开始一的上述步骤。当一5时t2又开始。
2. 解决安全问题。
3. 等待唤醒机制
4. 代码优化。
5. (必须掌握)练习:生产者消费者。
class ProducerConsumerDemo
{
publicstatic void main(String[] args)
{
Resourcer = new Resource();
Producerpro = new Producer(r);
Consumercon = new Consumer(r);
Threadt1 = new Thread(pro);
Threadt2 = new Thread(pro);
Threadt3 = new Thread(con);
Threadt4 = new Thread(con);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
/*
对于多个生产者和消费者。
为什么要定义while判断标记。
原因:让被唤醒的线程再一次判断标记。
为什么定义notifyAll,
因为需要唤醒对方线程。
因为只用notify,容易出现只唤醒本方线程的情况。导致程序中的所有线程都等待。
*/
class Resource
{
privateString name;
privateint count = 1;
privateboolean flag = false;
// t1 t2
publicsynchronized void set(String name)
{
while(flag)
try{this.wait();}catch(Exceptione){}//t1(放弃资格) t2(获取资格)
this.name= name+"--"+count++;
System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name);
flag= true;
this.notifyAll();
}
// t3 t4
publicsynchronized void out()
{
while(!flag)
try{wait();}catch(Exceptione){}//t3(放弃资格) t4(放弃资格)
System.out.println(Thread.currentThread().getName()+"...消费者........."+this.name);
flag= false;
this.notifyAll();
}
}
class Producer implements Runnable
{
privateResource res;
Producer(Resourceres)
{
this.res= res;
}
publicvoid run()
{
while(true)
{
res.set("+商品+");
}
}
}
class Consumer implements Runnable
{
privateResource res;
Consumer(Resourceres)
{
this.res= res;
}
publicvoid run()
{
while(true)
{
res.out();
}
}
}