在学习线程的是也许进程和线程两者经常搞混
进程:电脑上的程序或者软件,是一个一个的进程,具有独立的内存使用空间,使用的数据不会有冲突。
线程:在一个进程中,如果同时进行着两件或两件以上的事情,那么其中的每一件事情,就是一个线程。那么如果一个进程中,同时进行着三件事情,就是开辟了三个线程,这些线程共享一块内存空间。
为什么使用线程:1.在开发软件的时候提高流畅度
2.用户体验和客户需求等
两种创建线程方法的区别:
1.Runnable使用实现接口创建线程类的几率比较多一点,这样代码结构清晰,而且避免了java单继承的缺陷;
2.Thread继承访问线程属性会有点不同:一种使用this,一种使用Thread.currentThread()
线程的生命周期:
创建状态new -----就绪状态start()------运行状态running-----阻塞状态blocking------终止stop
经常使用的方法:
sleep();睡眠
getName();获取名字
yield();让出线程
Thread.currentThred().*();//这里的*是代表Thread里的所有类都可以使用;还有这里的currentThred()返回对当前正在执行的线程对象的引用
isAlive();判断是否活跃
priority();优先级方法
join();让这个线程先运行,等运行完了才轮到其他线程运行
notify();唤醒其他线程执行
notify();//唤醒全部线程
在学习中要注意的一些问题:
优先级,休眠,多线程营造代码顺序执行的效果,生命周期,两种创建线程方法的区别
如何在线程中访问线程信息等
下面是线程同步问题:
解决我一般使用synchronized来锁,也了解ReentranLock类
新建一个类
public class Ticket {
private int tid = 100;// 票的编号
public Ticket() {
super();
// TODO Auto-generated constructor stub
}
public Ticket(int tid) {
super();
this.tid = tid;
}
// 功能:售票
public synchronized void saleTicket() {
/*
* t1拿到了时间片:if(tid>0)为真,刚判断完,t2抢走了时间片,也执行if(tid>0)为真,然后都接着执行后面的代码,
* 于是打印的票的编号都是100
*/
//这是第一种解决方案:给对象上锁
/*synchronized (this) {
if (tid > 0) {
System.out.println(Thread.currentThread().getName() + "售出票的编号:"
+ tid);
--tid;
}
}*/
if (tid > 0) {
System.out.println(Thread.currentThread().getName() + "售出票的编号:"
+ tid);
--tid;
}
}
public class ShoupiaoWindow implements Runnable {
private Ticket ticket;
public ShoupiaoWindow(Ticket ticket) {
this.ticket = ticket;
}
@Override
public void run() {
// 售票:不是自己卖票,是调用Ticket类中的方法卖票
//不要锁大片代码,这里这样做就没有了多线程并发的效果
//synchronized (ticket) {
for (int i = 0; i < 100; i++) {
ticket.saleTicket();
}
//}
}
}
/**
* 线程同步
* 1.使用同步监视器,就是对象锁,方法锁的意思
* 2.怎么做:给对象上锁或者给方法上锁
* >>需要上锁的对象:它的数据出现问题,把造成问题出现的代码让他们全部执行完,这就是解决问题的最重要的策略
* >>需要上锁的方法:哪个方法出问题就上锁
*注意:
* 1.不要大面积的锁,粒度小点,只是锁出问题的代码
* 2.语法格式:
* 锁对象:synchronized (this){ //代码块 }
* 锁方法:public synchronized void 方法名(){ //代码块 }
*/
public class TestThreadSyc {
public static void main(String[] args) {
// 需求:模拟多个售票窗口卖票
Ticket ticket=new Ticket();
ShoupiaoWindow s1=new ShoupiaoWindow(ticket);
Thread t1=new Thread(s1);
t1.setName("售票窗口1");
t1.start();
ShoupiaoWindow s2=new ShoupiaoWindow(ticket);
Thread t2=new Thread(s2);
t2.setName("售票窗口2");
t2.start();
}
}
下面这个例子说明了线程交替运行
有一个银行账户,对应两个线程类,存钱线程和取钱线程
要求:存了钱马上通知取钱,取了钱马上通知存钱线程通信的原理是调用Object类提供的方法:
wait(等待,释放锁,必须别人唤醒他,他才进入就绪状态)
notify(唤醒在此对象监视器上等待的单个线程)
notifyAll(唤醒在此对象监视器上等待的所有线程)
建一个类
public class Account {
//余额
private double balance;
//旗标,就是标志的意思,flag为真,表示有钱,为假,表示没钱
private boolean flag=false;
//存钱
public synchronized void addMoney(double money)
{
if(!flag)
{
//余额为0,那么存钱,存完钱后,通知取钱
balance+=money;
System.out.println(Thread.currentThread().getName()+"线程存了钱,现在余额是"+balance);
flag=true;//这里必须修改flag的值,这样只有取钱线程可以取钱,存钱线程不可以执行
notifyAll();//通知正在等待此对象锁的其他线程
}else{
try {
wait();//等待,会释放同步监视器,也就是锁
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//取钱
public synchronized void getMoney(double money)
{
if(flag){
balance-=money;
System.out.println(Thread.currentThread().getName()+"线程取了钱,现在余额是"+balance);
flag=false;//这里必须修改flag的值,这样只有存钱线程可以存钱,取钱线程不可以执行
notifyAll();
}else
{
try {
wait();//没钱呀,只能等呀,等存钱线程存了钱通知我
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public Account() {
super();
}
public Account(double balance) {
super();
this.balance = balance;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
}
存钱的方法
public class AddMoney implements Runnable {
private Account account;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
account.addMoney(800);
}
}
public AddMoney() {
super();
// TODO Auto-generated constructor stub
}
public AddMoney(Account account) {
super();
this.account = account;
}
}
取钱的方法
public class GetMoney implements Runnable {
private Account account;
public GetMoney() {
super();
// TODO Auto-generated constructor stub
}
public GetMoney(Account account) {
super();
this.account = account;
}
@Override
public void run() {
// 取钱
for (int i = 0; i < 100; i++) {
account.getMoney(800);
}
}
}
测试类
public static void main(String[] args) {
// 若干个存钱线程,取钱线程操作同一个账户,看效果
Account account=new Account();
GetMoney g=new GetMoney(account);
Thread t1=new Thread(g);
t1.setName("取钱一:");
t1.start();
AddMoney a2=new AddMoney(account);
Thread t4=new Thread(a2);
t4.setName("存钱四:");
t4.start();
GetMoney g2=new GetMoney(account);
Thread t2=new Thread(g2);
t2.setName("取钱二:");
t2.start();
AddMoney a=new AddMoney(account);
Thread t3=new Thread(a);
t3.setName("存钱三:");
t3.start();}
下面这个是多线程买东西的一个小例子
public class Bread {
private int num;
public Bread(int num) {
super();
this.num = num;
}
public Bread() {
super();
// TODO Auto-generated constructor stub
}
// 生产方法
public synchronized void producerBread() {
num++;
System.out.println(Thread.currentThread().getName() + "生产面包,现在有" + num
+ "个");
}
// 消费方法
public synchronized void marketBread() {
if(num>0)
{
num--;
System.out.println(Thread.currentThread().getName() + "消费面包,还剩下" + num
+ "个");
}else
{
System.out.println(Thread.currentThread().getName()+"没有面包了...");
}
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
}
生产的线程
public class Producer implements Runnable {
private Bread bread;
public Producer(Bread bread) {
super();
this.bread = bread;
}
@Override//生产面包,循环调用20次生产方法
public void run() {
for(int i=0;i<20;i++)
{
bread.producerBread();
}
}
}
销售的线程
public class market implements Runnable {
private Bread bread;
public market(Bread bread) {
super();
this.bread = bread;
}
@Override//消费面包,循环调用20次生产方法
public void run() {
for(int i=0;i<20;i++)
{
bread.marketBread();
}
}
}
测试类
public class Test {
public static void main(String[] args) {
// 模拟面包生产销售
Bread b =new Bread();
System.out.println(b.getNum());
Producer p=new Producer(b);
Thread t1=new Thread(p);
t1.start();
market x1=new marketBread(b);
Thread t2=new Thread(x1);0
t2.start();market x2=new marketBread(b);
Thread t3=new Thread(x2);t3.start();}}
多线程售票的问题
1.卖票重复:
代码执行一半就被别的线程抢走了时间片,所以会出问题.
解决方法用用cynchronized锁住方法
public class Ticket {
private int tid = 100;// 票的编号
public Ticket() {
super();
// TODO Auto-generated constructor stub
}
public Ticket(int tid) {
super();
this.tid = tid;
}
public int getTid() {
return tid;
}
public void setTid(int tid) {
this.tid = tid;
}
// 功能:售票
public void saleTicket() {
/*
* t1拿到了时间片:if(tid>0)为真,刚判断完,t2抢走了时间片,也执行if(tid>0)为真,然后都接着执行后面的代码,
* 于是打印的票的编号都是100
*/
// 上锁,锁住这段代码,目的让他们一次执行完,而不是分开执行
// 1.给当前对象上锁,当前类Ticket,当前对象是谁?this
if (tid > 0) {
System.out.println(Thread.currentThread().getName() + "售出票的编号:"
+ tid);
--tid;
}
}
}
重写线程run()的方法
public class ShoupiaoWindow implements Runnable {
private Ticket ticket;
public ShoupiaoWindow(Ticket ticket) {
this.ticket = ticket;
}
@Override
public void run() {
// 售票:不是自己卖票,是调用Ticket类中的方法卖票
for (int i = 0; i < 100; i++) {
ticket.saleTicket();
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
测试类
public class Test {
public static void main(String[] args) {
// 需求:模拟多个售票窗口卖票
Ticket ticket=new Ticket();
ShoupiaoWindow s1=new ShoupiaoWindow(ticket);
Thread t1=new Thread(s1);
t1.setName("售票窗口1");
t1.start();
ShoupiaoWindow s2=new ShoupiaoWindow(ticket);
Thread t2=new Thread(s2);
t2.setName("售票窗口2");
t2.start();
}
}
总结:线程关键之处:
a>锁用来保护代码片段,任何时刻只能有一个线程执行被保护的代码。
b>锁可以阻挡(或是管理)试图进入被保护代码段的线程。
c>锁可以拥有一个或多个相关的条件对象。
d>每个条件对象管理那些已经进入被保护的代码段但还不能运行的线程。