知识点:线程
什么是进程?进程就是一个一个独立的应用程序,它们有独自的资源(变量,内存等),进程是数据独占的
什么是线程?线程是多个线程共享一块资源(变量),数据是共享的,那么这样的话,容易出现问题,比如卖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();
}
}