知识点:线程
什么是进程?进程就是一个一个独立的应用程序,它们有独自的资源(变量,内存等),进程是数据独占的
什么是线程?线程是多个线程共享一块资源(变量),数据是共享的,那么这样的话,容易出现问题,比如卖
2
个线程卖
100
张票,可能出现卖了大于
100
章票的情况,超出了临界资源,这样的话就不合理了,我们可以通过同步来处理这种数据并发,它依附于进程,是轻量级的进程
如何操作一个线程呢?通过
Thread
类的对象,这种对象叫做代理对象,通过这个对象可以操作线程,凡是访问
JVM
的外部资源的时候,都需要通过代理对象的方式访问
线程实现的两种方式:
1
.继承
Thread
类
2
.实例化该类或者是
Thread
类的对象
3
.对象
.Start()
1
.实现
Runnable
接口
2
.实例该类对象
3
.实例
Thread
对象,并把上面的实例当做参数传递给
Thread
构造器
两种方式的区别:
Runnable
的线程可以实现多继承,并可以很方便的共享数据,一个
Runnable
的实例可以构造出多个线程,相同点是都必须通过
Thread
来建立线程
例如:
继承
java.lang.Thread
:
class MyThread extends Thread{
public void run(){
需要进行执行的代码,如循环。
}
}
启动线程
public class TestThread{
public static void main(){
Thread t1=new Mythread();
T1.start();
}
}
实现
java.lang.Runnable
接口:
Class MyThread implements Runnable{
Public void run(){
}
}
这种实现可以再继承其他类。
启动线程时不同前者
public static void main(){
Runnable myThread = new MyThread();
Thread t = new Thread(myThread);
t.start();
}
当调用
start
方法时,
JVM
会到
OS
中产生一个线程。但是不一定马上运行该线程,只是准备就绪状态
/**
*
知识点:
*
线程的两种创建使用方法
* man1
类:采用第一继承
Thread
的方式
* man2
类:采用实现
Runnable
接口方式
* man3
类:继承
Thread
同时实现
Runnable
接口
*/
package MY.module09.tarena.two;
public class Man {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
// man1 m=new man1();//
第一种方法启动线程
// m.start();
// man2 m=new man2();//
第二种方法
// Thread t=new Thread(m);
// t.start();
man3 m=new man3();//
可以同时继承和实现
m.start();
while(true){
System.out.println("wan youxi......");
}
}
}
class man1 extends Thread{
public void run(){
while(true){
System.out.println("work........");
}
}
}
class man2 implements Runnable{
public void run() {
// TODO Auto-generated method stub
while(true){
System.out.println("work........");
}
}
}
class man3 extends Thread implements Runnable{
public void run() {
// TODO Auto-generated method stub
while(true){
System.out.println("work........");
}
}
}
Thread.currentThread().getName()
;
/**
*
知识点:
*
得到线程的名字
*
程序目标:
*
创造几个线程,在显示的时候能够看到线程的名字
*/
package MY.module09.tarena.currentThread;
public class People {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
P p1=new P();
P p2=new P();
p1.start();
p2.start();
String name=Thread.currentThread().getName();
while(true){
System.out.println(name+" xue xi");
}
}
}
class P extends Thread{
public void run(){
String name=Thread.currentThread().getName();
while(true){
System.out.println(name+" wan you xi.....");
}
}
}
看一个问题:有
100
张票,需要两个售票口去卖这
100
张票,我们写一个线程的程序来模拟这个操作
/**
*
发现问题:
*
程序目标:
*
造两个线程,让他们同时去卖
100
张票,可是最后卖了
200
张票,为什么呢
*
因为实例了两个对象
*
这样他们操作的不是同一个资源了(变量),没有数据并发
*/
package MY.module09.tarena.synchronizdes;
public class TestSaleTickets {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
//
使用继承
Thread
方式
//
我们会发现卖了
200
张票,为什么呢?因为实例了两个对象
//
这样他们操作的不是同一个资源了(变量),没有数据并发
SaleTicket st=new SaleTicket();
SaleTicket st2=new SaleTicket();
st.start();
st2.start();
//
使用实现
Runnable
接口方式
}
}
class SaleTicket extends Thread{
private int Ticket=1;
public void run() {
// TODO Auto-generated method stub
String name=Thread.currentThread().getName();
while(true){
if(Ticket<=100){//
只有
100
张票,这些资源是被线程共享的
System.out.println(name+":
销售了第
"+Ticket+++"
张票
");
}
}
}
}
//
这种方法可以数据并发,使用同一个资源,但是存在不安全问题
class SaleTicket2 implements Runnable{
private int Ticket=1;
public void run() {
// TODO Auto-generated method stub
String name=Thread.currentThread().getName();
while(true){
if(Ticket<=100){//
只有
100
张票,这些资源是被线程共享的
System.out.println(name+":
销售了第
"+Ticket+++"
张票
");
}
}
}
}
/**
*
发现问题:
*
通过使用
Runnable
方式,看似解决了使用同一资源的问题,但是里面
*
很不安全,例如:我们可以让线程休眠一下,会发现什么问题呢?
*/
package MY.module09.tarena.synchronizdes;
public class TestSaleTickets2 {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
SaleTicket3 st=new SaleTicket3();
Thread t1=new Thread(st);
Thread t2=new Thread(st);
t1.start();
t2.start();
}
}
class SaleTicket3 implements Runnable{
private int Ticket=1;
public void run() {
// TODO Auto-generated method stub
String name=Thread.currentThread().getName();
while(true){
if(Ticket<=100){//
只有
100
张票,这些资源是被线程共享的
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(name+":
销售了第
"+Ticket+++"
张票
");
}
}
}
}
上面那个程序总共销售了
101
张票,因为当有一个线程进入
if(Ticket<=100)
这里的时候,开始睡觉,另一个线程也进入了,然而只有一张票可以卖了,这是里面又有两个线程,造成了卖了
101
张票,如何解决这个问题呢?
我们可以通过同步块或同步方法来解决这个问题
两个线程修改共享资源时会出现数据的不一致,为避免这种现象采用对访问的线程做限制的方法。利用每个对象都有一个
monitor(
锁标记
)
,当线程拥有这个锁标记时才能访问这个资源,没有锁标记便进入锁池。
1
.
Synchronized
修饰代码块
public void push(char c){
synchronized(this){
...
}
}
对括号内的对象加锁,只有拿到锁标记的对象才能执行该代码块
2
.
Synchronized
修饰方法
public synchronized void push(char c) {
...
}
对当前对象的加锁,只有拿到锁标记的对象才能执行该方法
注:方法的
Synchronized
特性本身不会被继承,只能覆盖。
线程因为未拿到锁标记而发生阻塞进入锁池(lock pool)。每个对象都有自己的一个锁池的空间,用于放置等待运行的线程。由系统决定哪个线程拿到锁标记并运行。
上面的例子只有一个线程去卖票,不符合我们程序的要求,用以下方式解决
/**
*
知识点:
*
同步方法
*
程序目标:
*
两个线程共享
100
张票去销售这些票
*/
package MY.module09.tarena.synchronizdes;
public class TestSaleTickets3 {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
SaleTickets st=new SaleTickets();
Thread t1=new Thread(st);
Thread t2=new Thread(st);
t1.start();
t2.start();
}
}
class SaleTickets implements Runnable{
private int Tickets=1;
synchronized public void saleTicket() throws InterruptedException{
String name=Thread.currentThread().getName();
if(Tickets<=100){
Thread.sleep(1);
System.out.println(name+":
销售了第
"+Tickets+++"
票
");
}
}
public void run() {
// TODO Auto-generated method stub
while(true){
try {
this.saleTicket();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
死锁的形成:
/**
*
知识点:
*
死锁
* java
文件说明:
* ResouceA.java
* ResouceB.java
* TestResouce.java
*
程序目标:
*
让线程类的对象调用
A
类的
f1
方法,并传
B
类的引用过去,在
f1
方法中
*
会调用
B
类的方法,然而,当进入
f1
方法后,
*
该线程将休眠一会,目的是让另一个线程进入
B
类的
f2
方法,这个
f2
方法中
*
也会调用
A
类的一个方法,可是他们都会互相等待对方释放自己手中的
*
锁,因为这些方法都是同步方法,从而造成死锁,谁也不会释放资源
*/
package MY.module09.tarena.synchronizdes.sisuo;
public class TestResource implements Runnable{
private ResouceA a;
private ResouceB b;
public TestResource() {
a=new ResouceA();
b=new ResouceB();
new Thread(this).start();
a.f1(b);
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
TestResource t=new TestResource();
}
public void run() {
// TODO Auto-generated method stub
b.f2(a);
}
}
package MY.module09.tarena.synchronizdes.sisuo;
public class ResouceA {
synchronized public void f1(ResouceB b){
String name=Thread.currentThread().getName();
System.out.println(name+" f1()");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
b.rb();
}
synchronized public void ra(){
System.out.println("
先把货给我,我再给你钱
");
}
}
package MY.module09.tarena.synchronizdes.sisuo;
public class ResouceB {
synchronized public void f2(ResouceA a){
String name=Thread.currentThread().getName();
System.out.println(name+" f2()");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
a.ra();
}
synchronized public void rb(){
System.out.println("
先把钱给我,我再给你货
");
}
}
生产者
—
消费者模式:
/**
*
知识点:
*
生产-消费者模式 实现线程通讯
* Java
文件说明:
* Car.java
:封装了生产和销售汽车的方法
* Consumer.java
:消费汽车
* Producer.java
:生产汽车
* TestCommuniction.java
:主程序
*
程序目标:
*
当库存没有货了,开始生产汽车,如果有货,就不能生产了
*
只能销售,当没有库存了,便不能销售,通知开始生产
*/
package MY.module09.tarena.Communications;
public class TestCommunication {
public void test(){
Car car=new Car();
Consumer cs=new Consumer(car);
Producer pd=new Producer(car);
Thread tr=new Thread(cs);
pd.start();
tr.start();
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
TestCommunication t=new TestCommunication();
t.test();
}
}
package MY.module09.tarena.Communications;
public class Car {
private String name;
private double price;
private boolean isFull = false;
public Car() {
}
public Car(String name, double price) {
this.name = name;
this.price = price;
}
synchronized public void put(String name,double price){
if(isFull==true){//
如果库房已经满了
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
this.name=name;
this.price=price;
System.out.println("
生产出一量汽车
");
isFull=true;
notify();
}
synchronized public void get(){
if(!isFull){
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(name+":"+price);
System.out.println("
卖出一量车
");
isFull=false;
notify();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
package MY.module09.tarena.Communications;
public class Consumer implements Runnable{
private Car car;
public Consumer(Car car) {
super();
// TODO Auto-generated constructor stub
this.car = car;
}
public void run() {
// TODO Auto-generated method stub
while(true){
car.get();
}
}
}
package MY.module09.tarena.Communications;
public class Producer extends Thread{
private Car car;
public Producer(Car car) {
super();
// TODO Auto-generated constructor stub
this.car = car;
}
public void run(){
while(true){
car.put("Benz",10000);
}
}
}
线程的生命周期:
1.
new
线程类
:创建了一个线程
2.
线程对象
.start()
:线程准备就绪状态
3.
线程的调度器(类)会给准备就绪的线程分配资源(时间片,变量,内存等),分配后线程就可以运行了:
Running
状态也就是运行状态,也可以拿走这些资源,拿走后回到了准备就绪状态
4.
死亡状态:死后不能复活,当线程执行完毕后,死亡
5.
阻塞状态:
锁池,当有一个线程拿到锁了,另一个线程也想要得到,那他先在锁池中等待,等待另一个线程释放锁后,和其他线程共同抢这个锁,释放锁后回到可运行状态,等待调度器分配资源,可以再次抢这个锁
等待池,当线程遇到
wait
方法后,进入等待池,这个时候,需要其他线程使用
notify
或者
notifyall
方法唤醒,唤醒后回到锁池中,等待其他线程释放锁,当其他线程释放锁的时候,共同争夺对象锁,等待调度器分配资源,可以再次抢这个锁
notify:
随机挑选一个线程去唤醒(只能唤醒一个)
notifyall:
每个线程都叫一遍,最终只有一个被唤醒
休眠:
sleep
方法,进入休眠状态,睡醒后回到可运行状态,等待调度器分配资源,使用
interrupt()
方法可以将正在休眠的线程打醒
join()
阻塞状态:加入到一个线程
线程的停止:
Thread.stop();
/**
*
程序目标:
*
让线程停止
*
有个
Stop
方法,但是不推荐使用
*
我们可以用一个标记
boolean
类型的来控制程序的结束
*/
package MY.module09.tarena.ThreadStop;
public class ThreadStop extends Thread{
private boolean stop=true;
public void SStop(){
stop=false;
}
/**
* @param args
*/
@SuppressWarnings("deprecation")
public static void main(String[] args) {
// TODO Auto-generated method stub
ThreadStop t=new ThreadStop();
t.start();//
有可能
t0
先执行,也有可能
man
执行
try {
t.sleep(500);//
让
t0
线程去睡觉
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("man running");
t.SStop();
}
public void run(){
String name=Thread.currentThread().getName();
while(stop){
System.out.println(name+":runing......");
}
}
}
线程的优先级:
依赖于平台的,每个平台的优先级都不同
线程对象
.setName
给线程起名字
线程对象
.setPriority(Thread.Max);
/**
*
程序目标:
*
测试线程设置姓名和优先级
*/
package MY.module09.tarena.priority;
public class TestPriority implements Runnable{
private int i=1;
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
TestPriority tp1=new TestPriority();
Thread t1=new Thread(tp1);
Thread t2=new Thread(tp1);
t1.setName("t1");
t2.setName("t2");
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
}
public void run(){
String name=Thread.currentThread().getName();
while(i<=500){
System.out.println(name+":"+i);
i++;
}
}
}
join()
方法:
也叫等死,等插入的线程运行完死掉,再接着执行
/**
*
知识点:
* join()
*
程序目标:
*
两个线程,一个循环
2000
次,一个循环
200
次,当循环
2000
次的那个
*
线程,循环到
100
次的时候,让另一个线程插入,等他循环完,死掉
*
后,继续循环,直到死掉
*/
package MY.module09.tarena.join;
public class TestJoin implements Runnable{
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
TestJoin tj=new TestJoin();
Thread t1=new Thread(tj);
t1.start();
int i=0;
String name=Thread.currentThread().getName();
while(i<200){
if(i==100){
try {
t1.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(name+":"+i++);
}
}
public void run() {
// TODO Auto-generated method stub
int j=0;
String name=Thread.currentThread().getName();
while(j<2000){
System.out.println(name+":"+j++);
}
}
}
interrupt():打醒正在休眠的线程
/**
* 知识点:
* Interrupt():打醒正在休眠的线程
* currentTimeMillis():System中的方法,返回当前时间,以豪秒为单位
* 程序目标:
* 让一个线程睡20000豪秒,man线程睡2000豪秒,当man醒来,就打醒
* 另一个线程
*/
package MY.module09.tarena.interrupt;
public class TestInterrupt extends Thread{
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
TestInterrupt tl=new TestInterrupt();
tl.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("我醒了,你也别想睡");
tl.interrupt();
}
public void run(){
long startTime=System.currentTimeMillis();
System.out.println("我要睡觉了");
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
long endTime=System.currentTimeMillis()-startTime;
System.out.print("刚睡一会就被吵醒了"+endTime);
}
}
}
类锁:
什么是类锁?有什么用?什么情况下需要用类锁?
如果我们要访问静态的变量,是需要通过一个静态方法的,那么给静态方法加上synchronized,做数据并发处理,这个时候,线程得到的锁不是对象的锁,而是这个类的锁,因为这是synchronized修饰的是静态方法,静态方法属于类,不属于某一对象
/**
* 知识点:
* 类锁
* 程序目标:
* 两个线程访问另一个类的静态变量,当方法另一个类的静态方法的时候
* 得到静态变量的值,方法中的第一句是让静态变量加1,如果不使用同步
* 方法的话,因为休眠的缘故,会出现线程不安全的情况,所以必须在静态
* 方法上得加上琐,这个锁是类锁
*/
package MY.module09.tarena.classlock;
public class TestClassLock {
private static int i=1000;
public synchronized static int get(){
i++;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return i;
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
B t1=new B();
B t2=new B();
t1.start();
t2.start();
}
}
class B extends Thread{
TestClassLock tcl=new TestClassLock();
public void run(){
System.out.println(tcl.get());
}
}
懒汉单例模式:
为什么叫懒汉单例模式呢?
因为在属性中先不new对象,得别人需要的时候,在new对象
通过类锁可以实现懒汉单例模式
/**
* 知识点:
* 类锁,懒汉单例模式
*/
package MY.module09.tarena.danli2;
public class TestDanli {
private static TestDanli td=null;
private TestDanli() {
// TODO Auto-generated constructor stub
System.out.println("结婚了");
}
public synchronized static TestDanli getDanli(){
if(td==null){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
td=new TestDanli();
}
return td;
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
B t1=new B();
B t2=new B();
t1.start();
t2.start();
}
}
class B extends Thread{
public void run(){
TestDanli.getDanli();
}
}